Delphi передать переменную в dll

Обновлено: 04.07.2024

Использование DLL (динамически подключаемая библиотека) широко распространено в программировании Windows. DLL на самом деле часть кода исполняемого файла с расширением DLL. Любая программа может вызывать DLL.

Преимущество DLL заключается в следующем:

  • Повторное использование кода.
  • Совместное использование кода приложениями.
  • Разделение кода.
  • Улучшение потребления ресурсов в Windows.

Создание DLL

В меню File выберите пункт New -> Other . В диалоговом окне на вкладке New выберите DLL Wizard. Автоматически будет создан модуль – пустой шаблон будущей DLL.

Синтаксис DLL

Для того, чтобы построить DLL, выберите Project -> Build Имя_проекта.

Видимость функций и процедур

Функции и процедуры могут быть локальными и экспортируемыми из DLL.

Локальные

Локальные функции и процедуры могут быть использованы внутри DLL. Они видны только внутри библиотеки и ни одна программа не может их вызывать извне.

Экспортируемые

Экспортируемые функции и процедуры могут быть использованы за пределами DLL. Другие программы могут вызывать такие функции и процедуры.

Исходный код выше использует экспортируемую функцию. Имя функции следует за зарезервированным словом Exports.

Загрузка DLL

В Delphi есть два вида загрузки DLL:

  • Статическая загрузка
  • Динамическая загрузка

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

При запуске приложения загружается автоматически. Она остается в памяти на протяжении выполнения программы. Очень просто использовать. Просто добавьте слово external после объявления функции или процедуры.

Если DLL не будет найден, программа будет продолжать работать.

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

DLL загружается в память по мере необходимости. Ее реализация более сложная, потому что Вы сами должны загружать и выгружать ее из памяти. Память используется более экономно, поэтому приложение работает быстрее. Программист сам должен следить, чтобы все работало правильно. Для этого нужно:

  • Объявить тип описываемой функции или процедуры.
  • Загрузить библиотеку в память.
  • Получить адрес функции или процедуры в памяти.
  • Вызвать функцию или процедуру.
  • Выгрузить библиотеку из памяти.

Объявление типа, описывающего функцию

Загрузка библиотеки

Получаем указатель на функцию

Вызов функции

Выгрузка библиотеки из памяти

Динамический вызов DLL требует больше работы, но легче управлять ресурсами в памяти. Если Вы должны использовать DLL в пределах программы, тогда предпочтительнее статическая загрузка. Не забывайте использовать блок try…except и try…finally, чтобы избежать ошибок во время выполнения программы.

Экспорт строк

Созданная DLL с использованием Delphi, может быть использована и в программах, написанных на других языках программирования. По этой причине мы не можем использовать любой тип данных. Типы, которые существуют в Delphi могут отсутствовать в других языках. Желательно использовать собственных типы данных из Linux или Windows. Наша DLL может быть использована другим программистом, который использует другой язык программирования.

Можно использовать строки и динамические массивы в DLL, написанной в Delphi, но для этого нужно подключить модуль ShareMem в раздел uses в DLL и программе, которая будет ее использовать. Кроме того, это объявление должно быть первым в разделе uses каждого файла проекта.

Типов string, как мы знаем, С, С++ и других языках не существует, поэтому желательно использовать вместо них PChar.

Какого жору в себе не искать?

Может получить в длл, значение переменной из exe? А что собственно такое длл? Это тот же кусок кода, который грубо говоря "Аттачиться" в exe при загрузке. Это как бы одно целом можно так сказать.

ну это не трудно, сперва узнаем адрес глобальной переменной в exe.

к примеру в длл, есть функция:

где твой адрес заместо: $00AA0C

stdcall - не обязательно писать, в настройках и так по умолчанию все стоит.

Как получить адрес переменной в exe?

Легко: x := Cardinal(@value); Value - где ваша переменная, в x - будет занесен адрес. Это как бы приведение подобно в C++ reinterpret_cast.

У меня почему-то нету типа PInteger?

ну это не трудно объявить вверху:

Вот теперь есть.

Важно! Ваша exe должна обязательно загрузить длл

хммм, не пойму зачем такой индийский код, вроде на правки, на инжекты различного типа не похоже.
Возможно экскременты какие-то от рук студентов (проект вынос мозга), а хотя ладно, занимайтесь (но что такое dll надо по учить еще по лучше). =)

Последний раз редактировалось VintProg; 30.07.2012 в 04:26 . Причина: Да борщик с капусткой, вариться туда сюда бегаю, дописываю ответы,но борщик не красный? Ладно разберусь сам буду отлаживать.

Походу я хотел иметь суперглобальную переменную, к которой раз и обратился, и её значение(integer) хоть куда хоть где пиши. Это значение бы хранилось всегда в одном месте. Наверное именно это.

Короче я не знаю че мудрил, я все сделал, Значение offset определяется в процедуре dll(оно неизвестно заранее), потом переносится в X уже в exe-шники чтобы я мог фигачить его в едиты, листбоксы и прочее.
Правда, можно сразу брать из dll, но че меня так прет я не знаю

Ах, да, увидел в чем мой тупик, в функции __WriteBytes есть строка Seek(f,Offset), ясень пень из глобальной offset берется, в которой уже лежит. А я хочу чтобы из X бралось, я туда новый адресс суну. Вот, надо перегнать из X обратно в offset.

var
SomeGlobVarPtr: Pointer;
..
procedure SetGlobVarPtr(const Value: Pointer);
begin
SomeGlobVarPtr := Value;
end;

var
SomeGlobVar: Integer;
.

library sendket;
uses
SysUtils, Windows,dialogs,registry;

function Key_Hook(Code: integer; wParam: word; lParam: Longint): Longint;stdcall; export;
begin
.
end;

в DLL заведи глобальную переменную
MyValuePtr : PInteger;
перед первым вызовом Key_Hook передай в DLL отдельной процедурой
@MyValue из основной программы
SetGlobVarPtr(@MyValue);

var
capt,dir:string[250];
Foo: procedure(PtrCapt,PtrDir: PString);
begin
capt:="a";
dir:="b";
hookhandle:=0;
hinstDLL := LoadLibrary(pchar("sendkey.dll"));
Foo := GetProcAddress(hinstDLL, "SetGlobVarPtr");
Foo(@capt,@dd);
.
end;

Сама dll:
.
var
ss:string[250];
dirr,Capt : PString;

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

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;

procedure TForm1.Button1Click(Sender: TObject);
var
hook: MyProcType;
begin
@hook:= nil; // инициализируем переменную hook

Hdll:= LoadLibrary(PChar("hook_dll2.dll")); < загрузка DLL >
if Hdll > HINSTANCE_ERROR then < если всё без ошибок, то >
begin
@hook:=GetProcAddress(Hdll, "hook"); < получаем указатель на необходимую процедуру>
Button2.Enabled:=True;
Button1.Enabled:=False;
hook(true);
end
else
ShowMessage("Ошибка при загрузке DLL !");

procedure TForm1.Button2Click(Sender: TObject);
var
hook: MyProcType;
begin
@hook:= nil; // инициализируем переменную hook
if Hdll > HINSTANCE_ERROR then
begin < если всё без ошибок, то >
@hook:=GetProcAddress(Hdll, "hook"); < получаем указатель на необходимую процедуру>
Button1.Enabled:=True;
Button2.Enabled:=False;
hook(false);
end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeLibrary(Hdll); < при закрытии формы - освобождаем DLL >
end;

--------------DLL-----------------------
// Example2; Second DLL //
// @Aleksey Pavlov //
// Demo of correct hook - DLL module //

uses
Windows,
Messages,
Forms;

const
MMFName: PChar = "MyMMF2"; // имя объекта файлового отображения


type
PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = packed record
SysHook: HWND; // дескриптор установленной ловушки
end;

var
GlobalData: PGlobalDLLData;
MMFHandle: THandle;

dialog box-е, message box-е, menu, или scroll bar-е>
function SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
begin
if code = HC_ACTION then
begin

if TMsg(Pointer(lParam)^).message = WM_RBUTTONDOWN then

MessageBox(0, "HOOK2 working !", "Message from Exampel2/Process2", 0);
end;

Result:= CallNextHookEx(GlobalData^.SysHook, Code, wParam, lParam);
end;


procedure hook(switch : Boolean) export; stdcall;
begin
if switch=true then
begin

GlobalData^.SysHook := SetWindowsHookEx(WH_GETMESSAGE, @SysMsgProc, HInstance, 0);

if GlobalData^.SysHook <> 0 then
MessageBox(0, "HOOK2 установлен !", "Message from Exampel2/Process2", 0)
else
MessageBox(0, "HOOK2 установить не удалось !", "Message from Exampel2/Process2", 0);


if UnhookWindowsHookEx(GlobalData^.SysHook) then
MessageBox(0, "HOOK2 снят !", "Message from Exampel2/Process2", 0)
else
MessageBox(0, "HOOK2 снять не удалось !", "Message from Exampel2/Process2", 0);

procedure OpenGlobalData();
begin

// MMFHandle:= CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0, SizeOf(TGlobalDLLData), MMFName); // можно так, но лучше: см. след. строку
MMFHandle:= CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TGlobalDLLData), MMFName);

if MMFHandle = 0 then
MessageBox(0, "Can""t create FileMapping", "Message from Exampel2/Process2", 0);

на начало выделенного пространства>
GlobalData:= MapViewOfFile(MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TGlobalDLLData));
if GlobalData = nil then
begin
CloseHandle(MMFHandle);
MessageBox(0, "Can""t make MapViewOfFile", "Message from Exampel2/Process2", 0);
end;

procedure CloseGlobalData();
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MMFHandle);
end;

procedure DLLEntryPoint(dwReason: DWord); stdcall;
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenGlobalData;
DLL_PROCESS_DETACH: CloseGlobalData;
end;
end;

> те советы, что мне давали оказались неправильными,
> или я что-то не так понял,

Во-первых, главная ошибка - в вопросе было произнесено слово "глобальный", но ни слова не было сказано ни о глобальных хуках, ни о том, что глобальность требуется на уровне всей системы. Поэтому, естественно, вопрос все поняли так, что нужна глобальность на уровне лишь одной DLL - так и отвечали. Поэтому, если говорить об изначально поставленном вопросе, то ответы [1] и [3] уже были абсолютно верными.

Далее, в [4] Вы уточнили вопрос, упомянув о глобальном хуке (а это уже СОВСЕМ другая задача). Но снова сделали ошибку, причем очень характерную. Состоит она в том, Вы спросили не о том, как решить проблему ИСТИННУЮ, а о том, как решить ту проблему, которую Вы себе представили. Вот смотрите сами:
"Т.к. код выполняется каждый раз в разном пространстве, то мне нужно, чтобы в функции был доступ к переменной, которая задается где-нибудь в головной программе"

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

Это и была вторая ошибка - навязывание отвечающим ошибочного видения проблемы и ошибочного подхода к ее решению. Но была еще и ошибка третья.

До сих пор Вы ни разу не упоминали о строках и указателях на них. И лишь в [6] всплыл тип PString (кстати, Вы все же не расшифровали, что такое PString, поэтому я буду предполагать наиболее вероятный, с моей точки зрения, вариант - что это объявленный в SysUtils указатель на длинную строку: PString = ^string). Вот теперь мы и подошли к вопросу - почему не работал вариант [6]. Ниже я убрал из кода все, не относящееся к сути.

var
Capt: string[250];
Foo: procedure(PtrCapt: PString);
begin
Сapt := "a";
Foo(@Capt);

В последней строке уже две ошибки. Во первых, Вы передаете адрес короткой строки вместо адреса длинной - то есть, получаете прямую адресацию вместо заказанной Вами же косвенной (вспомним, что длинная строка - это УЖЕ указатель, поэтому PString - это указатель на указатель). Во-вторых, здесь передается ссылка на служебный нулевой символ короткой строки. Идем дальше и смотрим, к чему же это приводит.

var
SS: string[250];

procedure SetGlobVarPtr(PtrCapt: PString);
begin
SS := PtrCapt^; <----здесь вылетает ошибка

Далее программа честно пытается делает ей то, что Вы ей велели - она разыменовывает PtrCapt и хочет скопировать в строку SS 250 символов ASCIIZ-строки, которая находится по адресу, который сам записан по адресу $00000001. А эта область ей запрещена (нижние 4K под Win9x и нижние 64K под NT). Понятно, что система говорит "стой, не лезь куда не положено" - и генерит ошибку доступа к памяти. Приплыли.

очень простой пример. реализация клавиатурного хука через dll.

library Hook;
uses Windows, SysUtils, dialogs;
var
myHook: HHook = 0;
w1: longint =44;


function MsgProc(Code: integer; wParam: Word; lParam: Longint): Longint; stdcall;
begin
if (Code = HC_ACTION) and (((lParam shr 16) and KF_UP) = 0) then
begin
showmessage(inttostr(w1));
result := 1;
end else result := CallNextHookEx(0, Code, wParam, lParam);
end;

procedure setHook(Hook: boolean) stdcall;
begin
if Hook then
if myHook = 0 then myHook := SetWindowsHookEx(WH_KEYBOARD, @MsgProc, HInstance, 0)
else
begin
if myHook <> 0 then UnHookWindowsHookEx(myHook);
myHook := 0;
end;
end;
procedure start; stdcall;
begin
w1:=555555;
end;


exports setHook name 'SetHook', start name 'start';


в программе грузится статически.
procedure setHook(Hook: boolean) stdcall; external 'hook.dll' name 'SetHook';
procedure start; stdcall; external 'hook.dll' name 'start';
из программы при onshow идет start и потом sethook.
процедура start выполняется один раз при запуске программы. происходит присвоение глобальной переменной w1.

внимание вопрос.
когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
но когда без фокуса начальное значение переменной 44.
почему?

jcp>внимание вопрос.
jcp>когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
jcp>но когда без фокуса начальное значение переменной 44.
jcp>почему?

Потому, что у процесса, в контексте которого выполняется MsgProc переменная w1 не инициализирована (содержит мусор который компилятор записал в файл). Кратко: между процессами разделяется только код длл, но не данные.

Здравствуйте, Аноним, Вы писали:

jcp>>внимание вопрос.
jcp>>когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
jcp>>но когда без фокуса начальное значение переменной 44.
jcp>>почему?

А>Потому, что у процесса, в контексте которого выполняется MsgProc переменная w1 не инициализирована (содержит мусор который компилятор записал в файл). Кратко: между процессами разделяется только код длл, но не данные.


как же быть, если нужно в msgproc использовать данные ?

нифига не понятно. Почему w1:=555555; зашито в коде? чем это тогда отличается от константы?
и почему при установке хука значение не передавать?

Здравствуйте, Clickmaker, Вы писали:

C>нифига не понятно. Почему w1:=555555; зашито в коде? чем это тогда отличается от константы?
C>и почему при установке хука значение не передавать?

Нет блин я буду всю процедуру записи в w1 писать. все сократил по максимуму. оставив главное. когда w1 константа
все прекрасно пашет.
смею заметить что запись в эту переменную происходит при загрузке. до установки хука.

Здравствуйте, jcp, Вы писали:

jcp>Здравствуйте, Clickmaker, Вы писали:

C>>нифига не понятно. Почему w1:=555555; зашито в коде? чем это тогда отличается от константы?
C>>и почему при установке хука значение не передавать?

jcp>Нет блин я буду всю процедуру записи в w1 писать. все сократил по максимуму. оставив главное. когда w1 константа
jcp> все прекрасно пашет.
jcp>смею заметить что запись в эту переменную происходит при загрузке. до установки хука.

а так не пойдет?

function MsgProc(Code: integer; wParam: Word; lParam: Longint): Longint; stdcall;
begin
if w1 = -1 then begin
w1 := вычисляем w1
end;
.
end;

Здравствуйте, Clickmaker, Вы писали:

C>Здравствуйте, jcp, Вы писали:

jcp>>Здравствуйте, Clickmaker, Вы писали:

C>>>нифига не понятно. Почему w1:=555555; зашито в коде? чем это тогда отличается от константы?
C>>>и почему при установке хука значение не передавать?

jcp>>Нет блин я буду всю процедуру записи в w1 писать. все сократил по максимуму. оставив главное. когда w1 константа
jcp>> все прекрасно пашет.
jcp>>смею заметить что запись в эту переменную происходит при загрузке. до установки хука.

C>а так не пойдет?

C>function MsgProc(Code: integer; wParam: Word; lParam: Longint): Longint; stdcall;
C>begin
C> if w1 = -1 then begin
C> w1 := вычисляем w1
C> end;
C> .
C>end;


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

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Аноним, Вы писали:

jcp>>>внимание вопрос.
jcp>>>когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
jcp>>>но когда без фокуса начальное значение переменной 44.
jcp>>>почему?

А>>Потому, что у процесса, в контексте которого выполняется MsgProc переменная w1 не инициализирована (содержит мусор который компилятор записал в файл). Кратко: между процессами разделяется только код длл, но не данные.

А>как же быть, если нужно в msgproc использовать данные ?

да и еще кстати, если данные не разделяются, но константы то работают.

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Аноним, Вы писали:

jcp>>>внимание вопрос.
jcp>>>когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
jcp>>>но когда без фокуса начальное значение переменной 44.
jcp>>>почему?

А>>Потому, что у процесса, в контексте которого выполняется MsgProc переменная w1 не инициализирована (содержит мусор который компилятор записал в файл). Кратко: между процессами разделяется только код длл, но не данные.

А>как же быть, если нужно в msgproc использовать данные ?

Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, Аноним, Вы писали:

А>>Здравствуйте, Аноним, Вы писали:

jcp>>>>внимание вопрос.
jcp>>>>когда приложение, которое вызвало эту длл в фокусе, showmessage w1 показывает нужное значение 555555,
jcp>>>>но когда без фокуса начальное значение переменной 44.
jcp>>>>почему?

А>>>Потому, что у процесса, в контексте которого выполняется MsgProc переменная w1 не инициализирована (содержит мусор который компилятор записал в файл). Кратко: между процессами разделяется только код длл, но не данные.

спасибо, попробую. в делфи тоже есть такое.

Нету у дельфей такого. Предлагал пару лет назад на QC, сделать чтонить типа sharedvar, сказали некактуально Выход — шарить данные при помощи MMF. Хост процесс создает файл, длл открывает его в DLL_PROCESS_ATTACH.


если уж морочицо с шаред секцией, проще мою длл скомпилировать на vc ))) есть еще идеи ?

Здравствуйте, jcp, Вы писали:

jcp>если уж морочицо с шаред секцией, проще мою длл скомпилировать на vc ))) есть еще идеи ?

гуглить статью "Hooks — аспекты реализации"

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, jcp, Вы писали:

jcp>>если уж морочицо с шаред секцией, проще мою длл скомпилировать на vc ))) есть еще идеи ?

А>гуглить статью "Hooks — аспекты реализации"


спасибо друзья. сделал с mmf

jcp>считать их за один раз, с точки зрения оптимизации, лучше, чем постояно при каждом нажатии на клавишу.

там проверочка есть if w1 = -1. как раз, чтобы считать один раз. Ну по разу на процесс, хорошо, но не каждое нажатие.

Здравствуйте, Clickmaker, Вы писали:

jcp>>считать их за один раз, с точки зрения оптимизации, лучше, чем постояно при каждом нажатии на клавишу.

C>там проверочка есть if w1 = -1. как раз, чтобы считать один раз. Ну по разу на процесс, хорошо, но не каждое нажатие.

скомпилируй мой код, глянь, этот подход не рабочий. при каждом нажатии вне приложения будет w1 44 тк я задавал значение явно,
если убрать 44 то w1 будет 0. при каждом нажатии. проблему я решил. ответил выше. с помощью mmf

jcp>скомпилируй мой код, глянь, этот подход не рабочий. при каждом нажатии вне приложения будет w1 44 тк я задавал значение явно,
jcp>если убрать 44 то w1 будет 0. при каждом нажатии. проблему я решил. ответил выше. с помощью mmf

да почему не рабочий?
сначала w1 имеет значение 0 (если не указано явно), при загрузке хука процессом и первом нажатии проверяем: если 0, вычислили, сохранили.
При втором нажатии там уже не 0, поскольку dll уже в адресном пространстве процесса и глобальная переменная живет. Значит, повторно не вычисляем.

Здравствуйте, Clickmaker, Вы писали:

jcp>>скомпилируй мой код, глянь, этот подход не рабочий. при каждом нажатии вне приложения будет w1 44 тк я задавал значение явно,
jcp>>если убрать 44 то w1 будет 0. при каждом нажатии. проблему я решил. ответил выше. с помощью mmf

C>да почему не рабочий?
C>сначала w1 имеет значение 0 (если не указано явно), при загрузке хука процессом и первом нажатии проверяем: если 0, вычислили, сохранили.
C>При втором нажатии там уже не 0, поскольку dll уже в адресном пространстве процесса и глобальная переменная живет. Значит, повторно не вычисляем.

проверял на практике?

jcp>проверял на практике?
что тут проверять? каждый процесс имеет свою копию глобальных переменных в dll. При первом срабатывании хука достаточно один раз их проинициализировать.
Проблема может возникнуть, если данные нужно разделять именно между процессами. Если это твой случай — ну тогда сорри, неверно понял задачу

Здравствуйте, Clickmaker, Вы писали:

jcp>>проверял на практике?
C>что тут проверять? каждый процесс имеет свою копию глобальных переменных в dll. При первом срабатывании хука достаточно один раз их проинициализировать.
C>Проблема может возникнуть, если данные нужно разделять именно между процессами. Если это твой случай — ну тогда сорри, неверно понял задачу

конешно же ты прав.

Здравствуйте, jcp, Вы писали:

jcp>да и еще кстати, если данные не разделяются, но константы то работают.

Дык а что им будет? У каждого процесса — своя порция, но они же константы. Там значения сразу забиты в PE-секцию, никакого инициализирующего кода.

С переменными этот фокус не срабатывает, т.к. при аттаче DLL в другой процесс не срабатывает сгенеренный Delphi код инициализации для памяти, куда мапятся переменные этой DLL. В общем, упомянутая статья "Хуки — аспекты реализации" ответит на все вопросы.

Приведенный ниже текст подразумевает, что вы обладаете базовыми знаниями о принципе работы потоков и умеете создавать DLL.

Техническая сторона вопроса будет сфокусирована на потоках и функции DllEntryPoint. Функция DllEntryPoint не должна объявляться в ваших Delphi DLL. Фактически, большую часть, если не всю, Delphi DLL будет правильно работать и без вашего явного объявления DllEntryPoint. Тем не менее, я включил данный совет для тех Win32-программистов, которые понимают эту функцию и хотят связать с ней свое функциональное назначение, чтобы оно являлось частью DLL. Чтобы быть более конкрентым, это будет интересно тем программистам, которые хотят вызывать одну и ту же DLL из многочисленных потоков одной программы.

Исходный код данного проекта находится на FTP компании Borland. Данный код также доступен на Compuserve в секции Borland в виде файла BI42.ZIP.

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

// Расположенный здесь код выполняется в первую

// очередь при каждом вызове DLL любым exe-файлом.

Как вы можете здесь увидеть, здесь нет традиционного DLLEntryPoint, имеющегося в стандартных C/C++ DLL. Для тех, кто только начал изучать Win32, я сообщу, что DLLEntryPoint берет начало от функций LibMain и WEP, работающих в Windows 3.1. LibMain и WEP теперь считаются устаревшими, вместо них необходимо использовать DLLEntryPoint.

Для явной установки DLLEntryPoint в Delphi, используйте следующий код-скелет, имеющий преимущество перед переменной DLLProc, объявленной глобально в SYSTEM.PAS:

procedure DLLEntryPoint(Reason: DWORD);

// Здесь организуется блок Case для Dll_Process_Attach, и др.

// Здесь реализация экспортируемых функций

if DllProc = nil then begin

Данный код назначает объявленный пользователей метод с именем DLLEntryPoint объявленной глобально переменной Delphi с именем DllProc, в свою очередь объявленой в SYSTEM.PAS следующим образом:

Вы можете имитировать стандартную функциональность DLLEntryPoint, вызывая объявленный к тому времени локально DLLEntryPoint, и передавая ему Dll_Process_Attach в качестве переменной. В C/C++ DLL эта переменная должна передаваться определенной пользователем функции с именем DllEntryPoint автоматически при первом доступе к DLL из первой обратившейся к ней программы. В Delphi первый вызов этой функции может быть произведен вручную пользователем, но последующие вызовы происходят автоматически до тех пор, пока вы не назначите первый раз функцию переменной DllProc. Другими словами, вы можете форсировать первый вызов DllEntryPoint как показано выше, но последующие вызовы будут сделаны системой автоматически.

Dll_Process_Attach - одна из четырех возможных констант, которые система можете передавать функции DllEntryPoint. Эти константы объявлены в WINDOWS.PAS следующим образом:

DLL_PROCESS_ATTACH = 1 ; // Программа подключается к DLL

DLL_THREAD_ATTACH = 2 ; // Поток программы подключается к DLL

DLL_THREAD_DETACH = 3 ; // Поток "оставляет" DLL

DLL_PROCESS_DETACH = 0 ; // Exe "отсоединяется" от DLL

Более детальная скелетная конструкция DllEntryPoint с использованием приведенных констант:

procedure DLLEntryPoint(Reason: DWORD);

MessageBox(DLLHandle, 'Подключение процесса' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Подключение потока' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Отключение потока' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Отключение процесса' , 'Инфо' , mb_Ok);

В приведенном примере я просто вызываю диалог MessageBox в ответ на возможные параметры, передаваемые DLLEntryPoint. Тем не менее, вы могли бы найти более достойное применение данным константам или вовсе игнорировать их.

Работа с потоками

Приведенный ниже небольшой фрагмент кода достоин занять место в программе, вызывающей DLL. Он показывает как можно объявить функцию, экспортируемую из DLL, и как вызвать эту функцию из потока. Конечно, обычно нет необходимости вызывать функцию DLL из потока, я делаю это просто для того, чтобы показать функциональное назначение, связанное с обсуждаемыми выше константами Dll_Thread_Attach и Dll_Thread_Detach.

function MyFunc: ShortString; external 'DLLENTRY1' name 'MyFunc' ;

procedure ThreadFunc(P: Pointer); stdcall ;

S: array [ 0 .. 255 ] of Char;

MessageBox(Form1.Handle, S, 'Инфо' , mb_Ok);

HThread := CreateThread( nil , 0 , @ThreadFunc,

nil , 0 , ThreadID);

if HThread = 0 then ShowMessage( 'Нет потоков' );

Приведенный здесь код делится на три секции. В первой декларируется MyFunc, являющаяся простой реализацией функции в DLL. ThreadFunc сама располагается в отдельном потоке, создаваемом программой. Процедура UseThreadClick создает поток. Сразу после создания потока система вызывает процедуру ThreadFunc.

Вот декларация CreateThread:

lpThreadAttributes: Pointer; // атрибуты безопасности потока

dwStackSize: DWORD; // размер стека для потока

lpStartAddress: TFNThreadStartRoutine; // функция потока

lpParameter: Pointer; // аргумент для нового потока

dwCreationFlags: DWORD; // флаги создания

var lpThreadId: DWORD): // Возвращаемый идентификатор потока

THandle; // Возвращаемый дескриптор потока

В нормальной ситуации большинство параметров, передаваемых CreateThread, могут быть установлены в 0 или nil. Показан типичный пример вызова данной функции, но во многих случаях использование lpParameter неоправданно тяжело. Разумеется, любые переменные, установленные в данном параметре, передаются ThreadFunc в виде единственного аргумента.

Фактически, реализация функции потока очень проста, происходит вызов DLL и показывается информационный диалог, демонстрирующий строку, возвращаемую DLL.

Если вы создали программу с потоковой функцией как было показано выше, и создали DLL с функцией DLLEntryPoint, тоже показанной выше, то можно получить визуальное подтверждение того, как работает функция DLLEntryPoint. Поясняю: когда ваша программа загружается в память, DLL также должна быть автоматически загружена, тем самым вызывая MessageBox с текстом `Процесс подключен'. Диалоги появляются в зависимости от причины (Reason) вызова функции DllEntryPoint:

procedure DLLEntryPoint(Reason: DWORD);

MessageBox(DLLHandle, 'Процесс подключен' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Поток подключен' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Поток отключен' , 'Инфо' , mb_Ok);

MessageBox(DLLHandle, 'Процесс отключен' , 'Инфо' , mb_Ok);

Если вы создали процедуру ThreadFunc, показанную выше, то должно появиться диалоговое окно (MessageBox) с надписью "Поток подключен". При завершении работы подпрограммы ThreadFunc появится окошко с надписью "Поток отключен". Наконец, при закрытии программы должна появиться надпись "Процесс отключен". Пример, демонстрирующий процесс, доступен в сети.

Читайте также: