Домой! Динамически (т е в RunTime) подключаемые (присоединяемые - Link) библиотеки: DLL


Для начала - что это такое ?

Динамически присоединяемые библиотеки представляют собой модули, содержащие код, данные или ресурсы, которые могут совместно использоваться несколькими приложениями Windows. То есть эти: код, данные или ресурсы НЕ ВКЛЮЧАЮТСЯ в exe-файл программы, которая нуждается в этих: код, данные или ресурсы. Но эти код, данные или ресурсы программа (и не только одна) может использовать во время выполнения (за счет динамического присоединения (связывания)). Если несколько приложений используют одну и ту же DLL, то в оперативной памяти хранится лишь одна копия этой DLL. Операционная система существенно использует для своей работы DLL, основные из которых: Kernel32.dll (отвечает за управление памятью, процессами и потоками), User32.dll (содержит функции интерфейса пользователя, необходимые для создания окон и обработки сообщений Windows) и GDI32.dll (работа с графикой).
Обычно файлы библиотек DLL имеют расширение .dll. Но возможны и другие: .drv — драйверы устройств, .sys — системные файлы, .fon — файлы ресурсов шрифтов.

Далее разберемся: какая может быть польза от DLL

Пример

В качестве примера - программа Poisk, рассмотренная ранее. Из этой программы удалены подпрограммы-заглушки и лишние элементы интерфейса (TRadioGroup). Добавлен пункт меню: Данные | Новые.
Подпрограммы: RandomData и CalcMax размещены в библиотеке POISKLIB.dll, причем для разнообразия CalcMax преобразована в функцию. Процедура ShowGraphPoints осталась в модуле unPokaz, потому что в ней используется объект - форма, и работа с формами требует особого рассмотрения.
Самое забавное в этом проекте - согласно книге [6] он НЕ ДОЛЖЕН РАБОТАТЬ (но работает). Подробнее об этом см в конце страницы

Создаем библиотеку POISKLIB.dll

Запускаем Делфи, далее в меню: File | New | Other . Здесь на закладке New щелкаем DLL Wizard. В результате в редакторе создается шаблон файла .dpr и мы начинаем с ним работать. В итоге этот файл имеет вид:

library POISKLIB;
{$DEFINE POISKLIB}

{ Комментарий убрал }

uses
  SysUtils,
  Classes,
  UnPoiskIntface in 'UnPoiskIntface.pas';

procedure RandomData(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
 var Coord: TCoord); StdCall;
var ii: integer;
begin
  Randomize;
  for ii:=0 to NumPoints-1 do
    begin
      Coord[ii].X:= MinX + (MaxX-MinX)*random;
      Coord[ii].Y:= MinY + (MaxY-MinY)*random;
    end;
end;

function CalcMax(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
var Coord: TCoord; var N1,N2: integer {номера заданных точек}):word; StdCall;
var ii,jj,imax,jmax: integer; Rasst,RMax: double;
begin
  RMax:= 0; imax:=0; jmax:=0;
  for ii:=1 to NumPoints-1 do
    for jj:= ii+1 to NumPoints do
      begin
        Rasst:= sqrt(sqr(Coord[ii-1].X - Coord[jj-1].X)
            + sqr(Coord[ii-1].Y - Coord[jj-1].Y));
        if Rasst > Rmax then
          begin
            RMax:= Rasst;
            imax:= ii-1;
            jmax:= jj-1;
          end;
      end;
     N1:= imax;
     N2:= jmax;
     Result:= 222; 
end;
//ShowGraphPOints - недопустимо, т к там есть форма (frmPokaz.ClientWidth)

exports RandomData, CalcMax;

end.
В этот же проект добавляю модуль UnPoiskIntface, который будет интерфейсным для программы, использующей библиотеку POISKLIB. То есть в меню Делфи: File | New | Unit и создаю модуль (при этом многое копирую из модуля unPokaz проекта Poisk) , имеющий вид:
unit UnPoiskIntface;
{ Модуль интерфейса для библиотеки .DLL
  (по примеру из книги [6] )
}

interface

type TPointFloat = record
     X,Y: single;
     end;
     TCoord = array of TPointFloat;

{$IFNDEF POISKLIB}
procedure RandomData(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
 var Coord: TCoord); StdCall;
function CalcMax(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
var Coord: TCoord; var N1,N2: integer {номера заданных точек}):word;  StdCall;
{$ENDIF}

implementation

{$IFNDEF POISKLIB}
procedure RandomData; external 'POISKLIB.DLL' name 'RandomData';
function CalcMax; external 'POISKLIB.DLL' name 'CalcMax';
{$ENDIF}
end.
Примечание:
Директива компилятора $IFNDEF POISKLIB используется, так как нет необходимости включать объявление подпрограмм при компиляции модуля интерфейса для библиотеки. В результате можно применять объявления типа из модуля интерфейса совместно как с библиотекой, так и с любыми приложениями, которые будут использовать эту библиотеку.

Вношу исправления в проект Poisk и добавляю в него интерфейсный модуль UnPoiskIntface

Для этого проекта создаю папку \Применяем DLL. В нее копирую файлы проекта Poisk и редактирую эти файлы. Чтобы добавить модуль UnPoiskIntface - копирую UnPoiskIntface.pas из папки, в которой хранится проект POISKLIB (папка \Создаем DLL) в папку \Применяем DLL. Затем в меню Делфи: Project | Add to project - и выбираю UnPoiskIntface.pas . Ниже - см тексты модулей переделанного проекта Poisk:

unit unMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Menus;

type
  TfrmMain = class(TForm)
    MainMenu1: TMainMenu; Vvod: TMenuItem;
    Random: TMenuItem;    Solve: TMenuItem;
    Quit: TMenuItem;      Pokaz: TMenuItem;
    Label1: TLabel;       edCount: TEdit;
    Label2: TLabel;       edminX: TEdit;
    Label3: TLabel;       edmaxX: TEdit;
    Label4: TLabel;       edminY: TEdit;
    Label5: TLabel;       edmaxY: TEdit;
    btnNew: TMenuItem;
    procedure RandomClick(Sender: TObject);
    procedure QuitClick(Sender: TObject);
    procedure PokazClick(Sender: TObject);
    procedure SolveClick(Sender: TObject);
    procedure btnNewClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation uses unPokaz, UnPoiskIntface;

{$R *.dfm}

var
  NPoints, NN1, NN2: integer;
  MinX1,MaxX1,MinY1,MaxY1: double;
  Coord1: TCoord;
  Buf: word;

procedure TfrmMain.RandomClick(Sender: TObject);
begin
  NPoints:= StrToInt(edCount.Text);
  MinX1:= StrToFloat(edminX.Text);
  MaxX1:= StrToFloat(edmaxX.Text);
  MinY1:= StrToFloat(edminY.Text);
  MaxY1:= StrToFloat(edmaxY.Text);
  
if MaxX1 <= MinX1 then
  begin
    showmessage('MaxX > MinX !');
    exit;
  end;

if MaxY1 <= MinY1 then
  begin
    showmessage('MaxY > MinY !');
    exit;
  end;

  SetLength(Coord1, NPoints);
  Randomize;
  RandomData(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1);
  Solve.Enabled:= true;
  edCount.Enabled:= false;
  edminX.Enabled:= false;
  edmaxX.Enabled:= false;
  edminY.Enabled:= false;
  edmaxY.Enabled:= false;
end;

procedure TfrmMain.QuitClick(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TfrmMain.PokazClick(Sender: TObject);
begin
 ShowGraphPoints(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,0,0);
end;

procedure TfrmMain.SolveClick(Sender: TObject);
begin
  Buf:= CalcMax(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,NN1,NN2);
  ShowGraphPoints(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,NN1,NN2);
end;

procedure TfrmMain.btnNewClick(Sender: TObject);
begin
  edCount.Enabled:= true;
  edminX.Enabled:= true;
  edmaxX.Enabled:= true;
  edminY.Enabled:= true;
  edmaxY.Enabled:= true;
  Coord1:= nil; 
end;

end. 
 ---------------------------------
unit unPokaz;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, UnPoiskIntface;

type
  TfrmPokaz = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmPokaz: TfrmPokaz;

procedure ShowGraphPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
 var Coord: TCoord; N1,N2: integer);

implementation uses unMain;

{$R *.dfm}

procedure ShowGraphPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double;
var Coord: TCoord; N1,N2: integer);
var ii,XX,YY: integer;
  ax,bx,ay,by: double;
  { коэффициенты линейного преобразования реальных координат
    в экранные: Хэкр := ax*Хреал + bx; такие, что
    Хэкр(Xmin) = 0; Хэкр(Xmax) = ширина формы.
    Yэкр := ay*Yреал + by;
    Yэкр(Ymin) = высота формы; Yэкр(Ymax) = 0.
    a,b - определяются из решения СЛАУ, записанных выше 
  }
begin
//  showmessage('Показ точек - график');
 ax:= frmPokaz.ClientWidth/(MaxX-MinX);
 bx:= ax*MinX;
 ay:= frmPokaz.ClientHeight/(MinY - MaxY);
 by:= -ay*MaxY;

 with frmPOkaz do
   begin
     Canvas.Pen.Width:=2;
     Canvas.Pen.Color:= clGreen;
     Color:= clBlack;
     Canvas.FillRect(ClientRect);
     Show;
     for ii:= 0 to NumPoints - 1 do
       begin
       XX:= Round(ax*Coord[ii].X + bx);
       YY:= Round(ay*Coord[ii].Y + by);

       Canvas.Pixels[XX,YY ]:= RGB(200,200,200);
       Canvas.Pixels[XX+1,YY ]:= RGB(200,200,0);
       Canvas.Pixels[XX+1,YY+1 ]:= RGB(0,200,200);
       Canvas.Pixels[XX,YY+1 ]:= RGB(200,0,200);
       end;
      if N1<>N2 then
        begin
          XX:= Round(ax*Coord[N1].X + bx);
          YY:= Round(ay*Coord[N1].Y + by);
          Canvas.Ellipse(XX-5,YY-5,XX+5,YY+5);
          XX:= Round(ax*Coord[N2].X + bx);
          YY:= Round(ay*Coord[N2].Y + by);
          Canvas.Ellipse(XX-5,YY-5,XX+5,YY+5);
        end;
   end;
end;

end.
Текст модуля UnPoiskIntface показан выше, думаю - не стоит его повторять.

Примечание 1: (почему проект не должен работать):
В книге [6] есть предупреждение: если в DLL использует в качестве входного или выходного параметра процедуры/функции строки (длинные) или динамические массивы, то необходимо в самое начало предложения uses библиотеки (т е файла .dpr) и файла .dpr приложения, использующего такую DLL - включать модуль ShareMem. В этом проекте ShareMem не подключен и проект (у меня - D7, WinXP ) работает. С другой стороны, в комментарии, который среда Делфи (5,7) включает в текст библиотеки это предупреждение (ShareMem) касается только длинных строк, но не дин. массивов. С третьей стороны, авторы книги [6] авторитетные специалисты и вряд ли ошиблись. Так что если что - попробуйте ShareMem. Приведенные здесь примеры работают с ShareMem и без него.
Кроме того, авторы [6] предупреждают, что такие DLL могут работать только с приложениями Делфи. К [6] прилагается диск с примерами.

Примечание 2:
Использованный в этом проекте способ подключения подпрограмм из DLL в книге [5] называется статическим, а в книге [6] - неявным. Суть его в том, что библиотека подключается в момент загрузки приложения и остается подключенной все время его работы.
Иногда (если при некоторых запусках приложения библиотечные подпрограммы не нужны, если приложение использует несколько библиотек, причем каждую из них на короткое время) - целесообразно подключать библиотеку только на время работы импортируемой библиотечной подпрограммы. Такое подключение называется в [5] динамическим, а в [6] - явным. Для явного подключения используется эта же DLL.
Явное ( динамическое ) подключение DLL рассмотрено на следующей страничке.

Скачать RAR-архив исходников (оба проекта, каждый - в своей папке) + .exe(0.3 Mб)
Rambler's Top100
Hosted by uCoz