Информационные технологииStfw.Ru 🔍

Как можно использовать DLL

🕛 05.09.2006, 12:37
Хочу сразу оговориться и сказать, что нижеследующий текст предназначен для ещё не совсем опытных , но уже достаточно "продвинутых" программистов.
Он не претендует на полноту изложения по заданной теме и на "рэпирность" определений и высказываний, используемых автором.
На данный момент существует уже большое количество источников, в которых можно почитать много интересной информации о принципах написания и работе с библиотеками, но всё же, начиная разговор о использовании динамически компонуемых библиотеках, стоит ввести некоторые базовые определения и понятия, без которых дальнейшие выкладки будут не поняты людям, не имевшими дело с 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 может находиться только в адресном пространстве загрузившего её процесса, но никогда сама по себе !
Итак, считаем, что с ликбезом покончено.
Для чего нужны и как устроены динамически компануемые библиотеки можно прочитать у Тайксейры - там вся теория дана в лучшем виде.
Совсем неопытным в написании библиотек, а так же начинающим программистам - настоятельно рекомендую ознакомиться с общими принципами создания dll, описанными в статье Использование и создание dll в delphi.
Статьи Королевства по теме.
Для тех кто предпочитает c++ рекомендую прочитать статью Криса Касперски.
После ознакомления - обязательно возвращайтесь ;
)
Ну а теперь, пожалуй, приступим к самой теме данной статьи, а конкретнее к примерам применения и разъяснению этих примеров в меру авторской компетенции ;
)
Содержание:
Регистрация dll в системе ( или как бы нам, воспользовавшись особенностями приложения explorer.exe, заставить его загрузить нашу библиотеку в его адресное пространство).
Размещение модальных форм в dll.
Размещение "готовых" файловых образов библиотек в exe-файле с последующим их извлечением и использованием.
Как в своих dll использовать процедуры и ф-ии, находящиеся в других библиотеках.
Удаление программы "во время исполнения".
Ловушки в dll.
Регистрация dll в системе
( или как бы нам, воспользовавшись особенностями приложения explorer.exe, заставить его загрузить нашу библиотеку в его адресное пространство)
Часто возникают вопросы такого типа: "Как мне зарегистрировать свою dll в системе ?
Как загрузить свою dll при загрузке компьютера ?"
Для регистрации вашей dll в системе, необходимо:
в hkey_classes_rootclsid задать новый идентификатор класса (guid).
в hkey_local_machinesoftwaremicrosoftwindowscurrentversionshellserviceobjectdelayload создать раздел inprocserver32 и в значение этого раздела записать путь к вашей dll, которая должна грузиться при загрузке windows.
создать строковый параметр 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:tempmydll.dll');
reg.closekey;
reg.rootkey := hkey_local_machine;
reg.openkey('softwaremicrosoftwindowscurrentversionshellserviceobjectdelayload', true);
reg.writestring('mydllloade', '{69502f20-e8cd-11d5-a784-0050bf44bd3b}');
reg.closekey;

finally
reg.free;
end;
{try}
end;

end.
Размещение модальных форм в 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.
Размещение "готовых" файловых образов библиотек в 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 использовать процедуры и ф-ии,
находящиеся в других библиотеках
Не так давно один многообещающий ребёнок задал мне такой вопрос - "что можно предпринять, если моя программа статически грузит большое количество библиотек, и эти библиотеки тоже используют ф-ии, экспортируемые из других 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 ф-ии и процедуры.
Удаление программы "во время исполнения"
В название этого раздела вынесен вопрос, который ну если не каждую неделю задаётся в любом форуме, посвящённом программированию, то по крайней мере очень часто.
Бывают моменты в жизни каждого программиста, когда надо сделать что-то быстро, не привлекая особого внимания, не оставляя явных следов и не травмируя нежную психику пользователя :) Я не буду говорить про ring0 и про реализацию всего этого на asm-е,- нет, мы пойдём другим путём...
8)
К примеру нам надо добиться того, что бы наша программа при запуске без всяких проволочек стирала себя физически с винта, при этом продолжая выполнять какой-то код.
В лоб это сделать не получится, но что мешает исполнить необходимый нам код в контексте какого либо постороннего потока ?
В таком случае запущенный нами перед закрытием нашего процесса (в нашем случае процесса, загруженного из нашего exe-файла) код сможет удалить нужный нам файл, потому что данный код будет выполняться в адресном пространстве постороннего потока.
Может объяснение не очень удачное, но посмотрев на реализацию всего этого всё станет предельно ясно.
Вот алгоритм того, о чём я так долго распылялся: (это один из способов, наверняка не самый элегантный, но я не отступаю от темы статьи и упорно продолжаю совать эти грешные dll куда ни поподя :)
Извлечь из нашего exe модуля заранее помещённую туда dll (это мы уже умеем) и записать эту dll в любое место диска.
Загрузить 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 !).
Если общий подход ясен и у вас есть воображение, то можно добиться интересных результатов ;
)
Теперь поговорим о так называемых ловушках (hooks).
Тех, кто вообще не имеет представления о понятии hook-а в windows-кой интерпретации, я опять же отсылаю к научно-популярной литературе, потому что подробно объяснять механизм работы hook-ов я не буду (т.к.
эта тема выходит за рамки данной статьи), но из ниже приведённого кода и комментариев к нему, думаю, будет многое понятно для тех, кто знаком с термином hook, но кому не приходилось самому их программировать.
От слов к делу.
Сначала напишем основную часть кода - dll, в котором будем устанавливать и снимать 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: hwnd;
{ дескриптор загружаемой 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 на события клавиатуры, и пишут себе в файлик на диске всё, что пользователь набирает.
Но надо понимать, что hook может быть поставлен не только из dll, но и из обычного приложения.
На этом, пожалуй, и остановимся...
Все предложения, пожелания, нарекания и т.д.
присылайте мне, т.е.
автору :)
Все примеры опробованы и работают под win98 (на nt не пробовал).
Алексей Павлов
the adviser: - digitman
special thanks to: - fellomena
Источник:
http://delphirus.com.ru/

Pascal и Delphi   Теги:

Читать IT-новости в Telegram
Информационные технологии
Мы в соцсетях ✉