Потоки та DLL
Потоки та 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);
procedure TForm1.UseThreadClick(Sender: TObject);
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 із текстом `Процес підключений'. Діалоги з'являються залежно від причини виклику функції DllEntryPoint:
procedure DLLEntryPoint(Reason: DWORD);
MessageBox(DLLHandle, 'Процес підключений', 'Інфо', mb_Ok);
MessageBox(DLLHandle, 'Потік підключений', 'Інфо', mb_Ok);
MessageBox(DLLHandle, 'Потік вимкнений', 'Інфо', mb_Ok);
MessageBox(DLLHandle, 'Процес вимкнений', 'Інфо', mb_Ok);
Якщо ви створили процедуру ThreadFunc, показану вище, має з'явитися діалогове вікно (MessageBox) з написом "Потік підключений". Після завершення роботи підпрограми ThreadFunc з'явиться віконце з написом "Потік вимкнено". Нарешті, при закритті програми має з'явитися напис "Процес вимкнено". Приклад, що демонструє процес, доступний у мережі.