Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник |
Коллекция курьезных вопросов из форумов
KOL и MCK |
KOL и MCK - Компактные программы на Delphi
 
tenso-m.ru: железнодорожные весы от производителя

Эксплуатационные 'режимы' использования динамически компонуемых библиотек.

"...а сову эту мы ещё разъясним."
Булгаков "Собачье сердце".

В данной статье речь пойдёт, не о том как писать dll, а о том как их можно использовать. Стимулом к написанию данной статьи стали многочисленные вопросы в форумах, а так же постоянные нападки одной молодой особы :) Хочу сразу оговориться и сказать, что нижеследующий текст предназначен для ещё не совсем опытных, но уже достаточно "продвинутых" программистов. Он не претендует на полноту изложения по заданной теме и на "рэпирность" определений и высказываний, используемых автором.

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

Итак, dll - это, содержащие код, ресурсы или какие-то иные данные, программные модули. Чем нас не устраивают обычные exe-файлы, спросите вы. Отвечу,- основной смысл dll состоит в том, что ваши приложения могут загружать и обрабатывать код из библиотеки в процессе работы, а не на этапе сборки. ). Аббревиатура DLL говорит сама за себя - Dynamic Linked Library, ДИНАМИЧЕСКИ компонуемая библиотека. По структуре своей dll схожа с exe файлами, но когда ваша программа использует некую dll, другая программа тоже может использовать эту же dll, при этом в памяти будет физически загружена только одна копия используемой библиотеки, а адресное пространство, выделенное вашей программе, будет содержать образ/слепок/memory-mapped_file/экземпляр загруженной библиотеки. Не стоит забывать, что процессу, загрузившему dll, доступны функции, явно предоставляемые самой dll для "внешнего мира" - т.е. exports функции.

Так... теперь пару слов о способах загрузки dll. Не секрет, что существует два способа загрузки библиотек: статический и динамический (в литературе можно встретить названия - явный и неявный, но я предпочитаю придерживаться борландовской терминологии). Статический используется, как правило, тогда, когда в вашей библиотеке небольшое количество функций и процедур, которые вы наверняка собираетесь использовать в вашей программе. Если же ваша библиотека содержит большое количество процедур/функций или процедуры, вызываемые вами, используются вашей программой не часто (к примеру, имеет место сложная обработка графического изображения), то в данном случае целесообразнее использовать динамическую загрузку, дабы не загромождать память. К минусам статической загрузки можно отнести тот факт, что если при попытке вашей программы загрузить dll эта библиотека не будет найдена - вы получите ошибку, а программа просто-напросто не запустится. Если вы используете динамическую загрузку, то программа запустится в любом случае, но в момент, когда вы попробуете использовать функцию из отсутствующей dll, возникнет исключение, которое можно программно обработать и продолжить выполнение программы.

Статическая загрузка:

...
implementation

function ShowMyDialog(Msg: PChar): Boolean; stdcall; external 'project1.dll';
procedure SetValue(); cdecl; external 'some.dll'; 
... 

Тут, кажется не должно быть никаких неясностей - указываем название функции или процедуры, параметры, возвращаемый тип (для функции), способ передачи аргументов: stdcall - используется при вызовах WinAPI функций (передача параметров справа на лево), cdecl - при использовании dll, написанных на C++ ; есть ещё некоторые соглашения о вызовах о которых предлагаю вам почитать в справочной системе Delphi. Важно помнить, что если при разработке dll использовалось соглашение stdcall, то и при её вызове должно использоваться тоже stdcall ! Директива external указывает на местоположение библиотеки, из которой вы хотите экспортировать функцию.

Динамическая загрузка:

...
uses
...
type
{определяем процедурный тип, отражающий экспортируемую процедуру или функцию}
  TMyProcType = procedure (flag : Boolean); stdcall;
  TMyFuncType = function (Msg: PChar): Boolean; cdecl;
{эту операцию можно сделать непосредственно в разделе var процедуры, в которой вы будите загружать dll}
...
procedure TForm1.Button1Click(Sender: TObject);
var
SetValue: TMyProcType; {объявляем переменные процедурного типа}
ShowDialog: TMyFuncType; 

// или можно было сделать так (в таком случае type... не нужен): 
// SetValue: procedure (flag : Boolean); stdcall;
// ShowDialog: function (Msg: PChar): Boolean; cdecl; 

Handle01, Handle02: HWND; {дескрипторы, загружаемых библиотек} 

begin
Handle01:= LoadLibrary(PChar('project1.dll')); { загрузка dll }
try 
  @ShowDialog:=GetProcAddress(Handle01, 'ShowMyDialog'); { получаем указатель на необходимую процедуру}
  ShowDialog(PChar('Dll function is working !!!')); { используем функцию }
except
  ShowMessage('Ошибка при попытке использовать dll !');
finally
FreeLibrary(Handle01); { выгружаем dll }
end; { try }
end;
end. 

Аналогично и с процедурой SetValue();

Из всего вышесказанного необходимо понять, что при статической загрузке экспортируемая вами dll находится в памяти с момента запуска программы вплоть до Application.Terminate, и вам, как программисту, не надо следить за процессом освобождения памяти из под вашей dll. При динамической загрузке вы сами в любом месте программы загружаете dll, но и сами же должны следить за её выгрузкой. Если при динамическом способе загрузки вы самостоятельно, с помощью функции FreeLibrary, не выгрузите dll из памяти, то она будет находиться в адресном пространстве процесса, загруженного из вашей программы вплоть до его (процесса) уничтожения. Но в момент завершения работы программы память из под библиотеки будет освобождена, т.к. сама dll может находиться только в адресном пространстве загрузившего её процесса, но никогда сама по себе!

Итак, считаем, что с ликбезом покончено.

Для чего нужны и как устроены динамически компануемые библиотеки можно прочитать у Тайксейры (http://delphi.mtu-net.ru/library/index.htm#link3) - там вся теория дана в лучшем виде.
Совсем неопытным в написании библиотек, а так же начинающим программистам - настоятельно рекомендую ознакомиться с общими принципами создания dll, описанными в статье на delphi.mastak.ru (http://www.delphimaster.ru/articles/usedll/).
Для тех, кто предпочитает C++ рекомендую прочитать статью Криса Касперски (http://www.programme.ru/index.phtml?arch/072001/072001_1_1.htm).
После ознакомления - обязательно возвращайтесь ;)

Ну а теперь, пожалуй, приступим к самой теме данной статьи, а конкретнее к примерам применения и разъяснению этих примеров в меру авторской компетенции ;)


Содержание:

  1. Регистрация dll в системе (или как бы нам, воспользовавшись особенностями приложения Explorer.exe, заставить его загрузить нашу библиотеку в его адресное пространство).
  2. Размещение модальных форм в dll.
  3. Размещение "готовых" файловых образов библиотек в EXE-файле с последующим их извлечением и использованием.
  4. Как в своих dll использовать процедуры и функции, находящиеся в других библиотеках.
  5. Удаление программы "во время исполнения".
  6. Ловушки в dll.

1. Регистрация dll в системе ( или как бы нам, воспользовавшись особенностями приложения Explorer.exe, заставить его загрузить нашу библиотеку в его адресное пространство)

Часто возникают вопросы такого типа: "Как мне зарегистрировать свою dll в системе? Как загрузить свою dll при загрузке компьютера?" Для регистрации вашей dll в системе, необходимо:

  1. в HKEY_CLASSES_ROOT\CLSID задать новый идентификатор класса (GUID).
  2. в HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ShellServiceObjectDelayLoad создать раздел InProcServer32 и в значение этого раздела записать путь к вашей dll, которая должна грузиться при загрузке Windows.
  3. создать строковый параметр MyDllLoad со значением, равным определённому нами GUID.

Вот собственно и всё - теперь explorer, при каждом входе в систему, будет грузить вашу библиотеку (ВНИМАНИЕ!!! при таком способе регистрации загруженная dll не будет выгружаться из памяти до тех пор, пока процесс Explorer'а будет существовать хотя бы в одном экземпляре!)

Реализация данного способа имеет следующий вид:

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, registry;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var 
reg:Tregistry;

begin
reg:=Tregistry.create;
reg.rootkey:=HKEY_CLASSES_ROOT;

{Теперь настала пора создать идентификатор для нашей библиотеки. Идентификатор класса - это глобальный уникальный идентификатор, состоящий из шеснадцатиричных цифр. В него входят метка времени создания и адрес платы сетевого интерфейса, попросту говоря MAC, или фиктивный адрес платы при отсутствии оной в вашем компьютере 8) Получить этот идентификатор можно двумя путями: 1) в Delphi, в любом месте редактора кода нажать Ctrl-Shift-G. 2) или воспользоваться API функцией CoCreateGUID();}

try
 reg.openkey ('CLSID\{69502F20-E8CD-11D5-A784-0050BF44BD3B}\InProcServer32', true);
 reg.writestring('','C:\TEMP\MyDll.dll');
 reg.closekey;
 reg.rootkey:=HKEY_LOCAL_MACHINE;
 reg.openkey('Software\Microsoft\Windows\CurrentVersion\ShellServiceObjectDelayLoad', true);
 reg.writestring('MyDllLoade','{69502F20-E8CD-11D5-A784-0050BF44BD3B}');
 reg.closekey;

finally
 reg.free;
end; {try}

end;
end.

Исходник лежит здесь (runatstart.zip).

2. Размещение модальных форм в dll.

Этот вопрос не представляется особо интересным, и поэтому я буду предельно краток. Делаете New-> DLL, затем New-> Form, накидываем туда все, что душа пожелает, и поехали:

library ModelF;

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls,
  Unit1 in 'Unit1.pas' {Form1};

function ShowMyDialog(Msg: PChar): Boolean;  stdcall;
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;

exports ShowMyDialog;

begin
end.
И сам проект, содержащий вызывающий DLL код:
unit main2;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
function ShowMyDialog(Msg: PChar): Boolean;  stdcall; external 'project1.dll';
{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
if ShowMyDialog(PChar('Work !!!'))= TRUE then
ShowMessage('TRUE !')
ELSE
ShowMessage('FALSE !');
end;

end.
Исходник лежит здесь (modalforms.zip).

3. Размещение "готовых" файловых образов библиотек в EXE-файле с последующим их извлечением и использованием.

Представим такую ситуацию: ваша программа использует несколько dll-ок, не вами написанных, и у вас нет их исходного кода (т.е. непосредственно код процедур и функций, вами используемых в своей программе, вам не доступен), а вам ну просто "противопоказано" показывать эти библиотеке пользователю (мало ли - забудет он их переписать вместе с программой, у dll файлов, как правило, атрибуты невидимости стоят; или ещё какая-нибудь причина на то имеется - мало ли ;)

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

Вы можете сказать, что в таком случае сам exe файл увеличится в размерах и что можно использовать функции библиотек, размещённых в exe файле без переноса их на диск. На это можно ответить только одно - всё это конечно так, но ведь цель данной статьи наметить подходы к решению проблем, которые могут возникнуть при решении тех или иных задач, а отнюдь не дать готовые рецепты для какого-то конкретного случая...

Итак, приступим: 1) Открываем самый мощный текстовый редактор - Блокнот и начинаем ваять:
MYDLL RCDATA
mydll.dll
Записываем всё это как Lib.rc

2) Теперь для получения файла-ресурсов компилируем получившийся у нас Lib.rc :
brcc32.exe Lib.rc

3) Получили Lib.res, который необходимо прикрепить к нашему проекту, для этого используем директиву {$R Lib.res}

Нижеследующий код иллюстрирует, как можно прикрепить *.res файл к проекту и извлечь его при необходимости:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM} {$R Lib.RES}

procedure TForm1.Button1Click(Sender: TObject);
var
MyDll1: TResourceStream;
begin
MyDll1:=TResourceStream.Create(hInstance, 'MYDLL', RT_RCDATA);
try
  MyDll1.SaveToFile('duck.dll');
finally
  MyDll1.Free;
end;{try}

end;
end.

В результате сборки получим EXE-файл. При нажатии на кнопку формы будет создан файл duck.dll в той же директории, из которой была запущена программа (помните, что если dll-файлы в вашей системе имеют атрибут скрытых,- созданный duck.dll тоже будет невидимым).
Исходник лежит здесь (dll_res.zip).

4. Как в своих dll использовать процедуры и функции, находящиеся в других библиотеках.

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

Для ответа на этот вопрос необходимо помнить, что связь между вызываемой функцией и её исполняемым кодом устанавливается во время выполнения приложения путём использования ссылки на конкретную функцию dll и при этом не важно, будут ли эти ссылки объявлены в самом приложении или в каком-то внешнем модуле. К примеру, посмотрите, как выглядит файл windows.pas,- ближе к концу файла вы можете увидеть, каким образом вызываются различные функции из библиотеки advapi32.dll .

Итак, создаём новый unit и объявляем импортируемые функции и процедуры и определяем все необходимые типы для ваших dll. К примеру, если у вас есть три библиотеки: 001.dll, 002.dll, 003.dll и в них находятся n-ое кол-во функций и процедур, которые вы намереваетесь использовать в своей программе, то создаём новый unit и... :

unit gather_it;

interface

function MyFunc001(i: integer): PChar;
procedure MyProc002 ();
function MyFunc003(): Integer;
// ..........................
procedure Something();

implementation

function MyFunc001; external '001.dll';
procedure MyProc002; external '002.dll';
function MyFunc003; external '003.dll';
// ..........................
procedure Something; external 'some_other.dll';

end.

Как видно из этого примера - в этом unit-е нигде нет реализаций самих функций, а лишь указания на то где эти функции находятся. Для использования данного модуля просто прописываем его название (unit gather_it) в раздел uses того модуля в котором предполагается использовать описанные в unit gather_it функции и процедуры.

5. Удаление программы "во время исполнения".

В название этого раздела вынесен вопрос, который ну если не каждую неделю задаётся в любом форуме, посвящённом программированию, то, по крайней мере, очень часто. Бывают моменты в жизни каждого программиста, когда надо сделать что-то быстро, не привлекая особого внимания, не оставляя явных следов и не травмируя нежную психику пользователя :) Я не буду говорить про ring0 и про реализацию всего этого на asm-е,- нет, мы пойдём другим путём... :)

К примеру, нам надо добиться того, что бы наша программа при запуске без всяких проволочек стирала себя физически с винта, при этом, продолжая выполнять какой-то код. В лоб это сделать не получится, но что мешает исполнить необходимый нам код в контексте какого либо постороннего потока? В таком случае, запущенный нами перед закрытием нашего процесса (в нашем случае процесса, загруженного из нашего exe-файла) код, сможет удалить нужный нам файл, потому что данный код будет выполняться в адресном пространстве постороннего потока. Может объяснение не очень удачное, но, посмотрев на реализацию всего этого, всё станет предельно ясно.

Вот алгоритм того, о чём я так долго распылялся: (это один из способов, наверняка не самый элегантный, но я не отступаю от темы статьи и упорно продолжаю совать эти грешные dll куда ни попадя :)
1). Извлечь из нашего exe модуля заранее помещённую туда dll (это мы уже умеем) и записать эту dll в любое место диска.
2). Загрузить dll в адресное пространство НЕнашего потока и выполнить процедуру из этой библиотеки, которая сотрёт исходный файл.
Сделать это можно, используя rundll32.exe примерно следующим образом:
- для начала напишем небольшую библиотеку:

************************ DLL ************************
library test1;

uses
  Windows;

procedure test(); stdcall;
begin
//Sleep(5); {можно задерживать выполнение кода при необходимости 8}
DeleteFile(PChar('D:\Project1.exe'));
{
ВНИМАНИЕ, до тех пор, пока выполняющийся процесс, загруженный из Project1.exe,
не выполнит команду FreeLibrary(собственный экземпляр) в Win9x/Me либо UnmapViewOfFile в 
NT/W2k, вы будете получать ошибку. Связанно это с тем, что эти вызовы ни в том ни в другом виде не 
вставляются автоматически компилятором Делфи в эпилог приложения. 
Вместо этого после выполнения эпилогом приложения вызова ExitProcess() ОС автоматически разорвет связь 
между процессом и файлом, вызвав ту или иную из вышеуказанных API-ф-ций. 
}
FreeLibrary(GetModuleHandle(NIL));
end;

exports test;

begin
// MessageBox(Handle, 'Library loaded !', 'Yeh... !!!', 0); 
end.
************************ DLL ************************
- ну а та часть exe файла, которая отвечает за загрузку dll, может выглядеть так:
...
procedure TForm1.Button1Click(Sender: TObject);
begin
ShellExecute(0, Nil,'rundll32.exe', ' test1.dll,test', Nil, SW_ShowNormal);
Close;
end;
...

Заметьте, что в нашей программе мы нигде саму библиотеку не загружаем.
rundll32.exe запускается следующим образом: rundll32.exe имя_dll, имя_процедуры, параметры. Таким образом в процедуру test мы можем передавать, к примеру, путь к файлу, который нужно стереть (не забывайте, что String не применим в случае если не используется юнит ShareMem, и надо использовать PChar).

Из примера видно, что при запуске Project1.exe из корневого каталога диска D:\ и нажатии на Button1, rundll32 загрузит библиотеку test.dll (если она тоже лежит в корне диска D:\), выполнит процедуру test, которая удалит Project1.exe и освободит память.

Кстати, загружаемую dll не видит ни TaskManager ни WinSight32 (это конечно не значит, что таким образом можно скрыть dll от NtQuerySystemInformation в NT или CreateToolhelp32Snapshot в Win9x !).

Если общий подход ясен и у вас есть воображение, то можно добиться интересных результатов ;)
Исходник лежит здесь (selfdelete.zip).

6. Ловушки в dll.

Теперь поговорим о так называемых ловушках (hooks).

Тех, кто вообще не имеет представления о понятии hook-а в Windows-кой интерпретации, я опять же отсылаю к научно-популярной литературе, потому что подробно объяснять механизм работы hook-ов я не буду (т.к. эта тема выходит за рамки данной статьи), но из ниже приведённого кода и комментариев к нему, думаю, будет многое понятно для тех, кто знаком с термином hook, но кому не приходилось самому их программировать. От слов к делу. Сначала напишем основную часть кода - dll, в котором будем устанавливать и снимать hook-и, а так же обрабатывать полученные сообщения:
(нижеследующий пример носит показательный характер, поэтому автор намеренно упростил код; так что этот пример не следует использовать как шаблон при написании собственных hook-ов, о причине такого ограничения читайте ниже)

library hook_dll;

uses Windows, Messages;

var
SysHook : HHook = 0;
Wnd : Hwnd = 0;

{ данная ф-ия вызывается системой каждый раз, когда возникает какое-то событие в
dialog box-е, message box-е, menu, или scroll bar-е}
function SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
begin
{ Передаём сообщение дальше по цепочке hook-ов. }
CallNextHookEx(SysHook, Code, wParam, lParam);
{ флаг code определяет тип произошедшего события. }
if code = HC_ACTION then
begin
{ В wnd кладу дескриптор того окна, которое сгенерировало сообщение.}
Wnd:=TMsg(Pointer(lParam)^).hwnd;
{ Проверяю, нажата ли правая кнопка мыши}
if TMsg(Pointer(lParam)^).message = WM_RBUTTONDOWN then
begin
{ Раскрываю окно на всю клиентскую область.}
ShowWindow(wnd, SW_MAXIMIZE);
{ Вывожу сообщение.}
MessageBox(0, 'HOOK is working !', 'Message', 0);
end;
end;
end;

{ Процедура установки HOOK-а}

procedure hook(switch : Boolean) export; stdcall;
begin
if switch=true then
begin
{ Устанавливаю HOOK, если он не установлен (switch=true). }
SysHook := SetWindowsHookEx(WH_GETMESSAGE, @SysMsgProc, HInstance, 0);
{ тут: WH_GETMESSAGE - тип hook-а ; @SysMsgProc - адрес процедуры обработки ;
  HInstance - указывает на DLL, содержащую процедуру обработки hook-а; последний
  параметр указывает на thread, с которым ассоциирована процедура обработки hook-а;
}
MessageBox(0, 'HOOK установлен !', 'Сообщение из DLL', 0);
end
else
begin
{ Снимаю HOOK, если он установлен (switch=false). }
UnhookWindowsHookEx(SysHook);
MessageBox(0, 'HOOK снят !', 'Сообщение из DLL', 0);
SysHook := 0;
end;
end;

exports hook;

begin
//MessageBox(0, 'MESSAGE FROM DLL  - loaded !', 'Message', 0);
end.
Так... с этим покончили; теперь код модуля, откуда будем вызывать нашу процедуру hook (для разнообразия буду использовать "динамическую" загрузку dll):
unit Unit1;

interface

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

{для динамической загрузки dll}
type
 MyProcType = procedure (flag : Boolean); stdcall;
{*****************************}

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Hdll : THandle; { дескриптор загружаемой dll-ки (для динамической загрузки)}

implementation
{ раскоментируйте эту строку для статической загрузки }
//procedure hook(state: boolean); stdcall; external 'hook_dll.dll';

{$R *.DFM}


procedure TForm1.Button1Click(Sender: TObject);
var
hook: MyProcType; {для динамической загрузки}

begin
{ раскоментируйте эту строку для статической загрузки }
//hook(true);

{ ********* динамическая загрузка **************}
Hdll:= LoadLibrary(PChar('hook_dll.dll')); { загрузка dll }
 if Hdll > HINSTANCE_ERROR then            { если всё без ошибок, то }
 @hook:=GetProcAddress(Hdll, 'hook')       { получаем указатель на необходимую процедуру}
 else
 ShowMessage('Ошибка при загрузке dll !');
{ **********************************************}

 hook(true);

Button2.Enabled:=True;
Button1.Enabled:=False;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
hook: MyProcType; {для динамической загрузки}

begin
{ раскоментируйте эту строку для статической загрузки }
//hook(false);

{ ********* динамическая загрузка **************}
hook:=GetProcAddress(Hdll, 'hook'); { получаем указатель на необходимую процедуру}
{ **********************************************}
hook(false); { обращение к процедуре }

Button1.Enabled:=True;
Button2.Enabled:=False;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeLibrary(Hdll); { при закрытии формы - выгружаем dll }
end;

end.

Как видно из примера - после установки hook-а, при нажатии правой кнопки мыши на элементах dialog box-а, message box-а и menu, то окно (окно в Windows понимании ;), над которым был произведён клик - займёт всю клиентскую область.

При установке нескольких hook-ов из dll необходимо учитывать одну особенность. Дело в том, что новые экземпляры dll с ловушками будут загружаться в адресное пространство каждого процесса в отдельности, в результате чего ловушки не смогут непосредственно общаться с приложением, их установившем. Вы можете сами в этом убедиться, установив две-три разные ловушки вышеуказанным способом. После установки всех hook-ов отрабатывать будет только один - стоящий в конце цепочки. Дело заключается в том, что переменная SysHook получит правильное значение лишь в контексте того процесса, который вызвал SetWindowsHookEx. В остальных процессах она останется нулем и поэтому для них правильный вызов CallNextHookEx окажется невозможным. Таким образом, предыдущая ловушка (если она была) для всех процессов, кроме одного, оказывается фактически отключенной, что может привести к нежелательным последствиям. (спасибо Юрию Зотову за помощь в данной области). Что бы избежать подобной ситуации, необходимо сохранять значение, возвращаемое функцией SetWindowsHookEx, для дальнейшего использования в глобальной переменной. Для этих целей можно использовать области разделяемой памяти (файлы отображённые в память). Данная тема выходит за рамки данной статьи, но автор обязательно осветит её в дальнейшем.

Исходник лежит здесь (hooks.zip).

Кстати говоря, почти все клавиатурные шпионы работают примерно по тому же принципу - устанавливают hook на события клавиатуры, и пишут себе в файл на диске всё, что пользователь набирает. Но надо понимать, что hook может быть поставлен не только из dll, но и из обычного приложения.

На этом, пожалуй, и остановимся... Все предложения, пожелания, нарекания и т.д. присылайте мне, т.е. автору :)
Все примеры опробованы и работают под Win98 (на NT не пробовал).

© Written and designed by Aleksey Pavlov. All rights reserved. 2001 ©
The adviser: - Digitman
Special thanks to: - Fellomena

   Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи:  Алексей Павлов
  

Другие статьи Наверх


  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо