Вікна, winapi, delphi

Коли це все-таки потрібно

Бувають випадки, коли програмісту хочеться відмовитися від VCL. Найчастіше звичайно це буває через економію, щоб exe виходив не такий великий (що в нинішнє століття вже практично безглуздо, тільки якщо ви займаєтеся будь-якої особливої ​​областю, наприклад демосцену). Але у VCL є ще одна неприємна обмеження - він однопотоковий. Сама Windows прекрасно підтримує багатопоточність, і коли хочеться чесної паралельної роботи віконець, доводиться використовувати API.

Як цього домагаються

Що зазвичай робить програміст, який завжди працював з VCL, і тут йому потрібні віконця на API? Він йде в гугл за прикладами, і в мсдн за описом функцій. А в інтернеті все приклади як на зло використовують процедурне програмування. І коли програміст таки прикручує подібний код - ділянку програми з цим кодом перетворюється в монстра. А найголовніше - не зовсім зрозуміло як подібний код можна покласти на концепцію ООП. Так, наприклад, як це зроблено в VCL. Хочеться створити віконце через TMyWindow.Create, хочеться працювати з віконцем всередині класу, як в VCL. Хочеться, врешті-решт, обробляти повідомлення message методами.

А чого доб'ємося ми

У цій статті я винайду велосипед постараюся зробити щось подібне VCL структурі, а саме - укласти наші віконця в концепцію ООП. Ну і опишу деякі принципи роботи Windows і віконних повідомлень. Ну а оскільки я вважаю, що проробляти будь-яку роботу треба з користю, то ми постараємося зробити reused friendly код, який можна буде легко використовувати і модифікувати в своїх майбутніх проектах, і використовувати, використовувати, використовувати.

Як віконця працюють в Windows

Той, хто хоч раз робив віконце засобами WinAPI, знає, що потрібно створити функцію, в яку будуть приходити наші повідомлення, зареєструвати клас з цією функцією, після цього створити віконце на основі цього класу, і, нарешті, в коді потоку зробити нескінченний цикл, який би тільки і займався тим, що вибирав віконні повідомлення. І це все дуже незручно. По-перше, тому що незрозуміло, як красиво з точки зору коду (а ще краще з точки зору ООП) створити 3, 4 або більше віконець. Цикл то у нас один, і віконна функція теж одна.

Незважаючи на те, що пишуть, мовляв, Windows посилає повідомлення вікна - це не зовсім так. Windows посилає повідомлення потоку. Всі створені вікна - прив'язуються до конкретного потоку, в якому викликалася функція CreateWindow. Важливо розуміти, що якщо ми створили вікно в поточному потоці, то і всі повідомлення будуть оброблятися тільки в контексті даного потоку. Саме так Windows і реалізує многопоточную роботу з вікнами. Отже, якщо у нас в одному потоці створено 10 віконець - то всі ці повідомлення прийдуть в один потік, і вибирати їх треба в одному циклі цього потоку. Це далі, при вибірці можна задати або фільтр по хендла (другий параметр GetMessage), або у віконній функції стає видно, якого віконця прийшло повідомлення. Але обробляти потрібно не повідомлення вікна, а повідомлення потоку, тобто другий параметр GetMessage повинен бути у нас 0. І це важливо, тому що ми повинні обробляти повідомлення WM_QUIT. Це повідомлення ніколи не потрапить ніякому вікна, і єдиний можливий варіант вибрати це повідомлення - передати 0 другим параметром функції GetMessage. Навіщо ж потрібно це дивне повідомлення. А потрібно воно щоб коректно вийти з віконного циклу. Коли ми робимо вибірку з GetMessage, то функція завжди повертає нам true, але коли GetMessage вибирає з черги WM_QUIT - повертає false. Якщо ми захотіли раптом вийти з віконного циклу раз і назавжди - то нам потрібно викликати в цьому ж потоці PostQuitMessage (ExitCode); і повідомлення WM_QUIT стане в чергу поточного потоку.

Як працюють віконця в VCL

Крім цього TApplication занадто обріс іншим функціоналом, і переписати код VCL для багатопотокового підтримки дуже складно. На щастя ми й не будемо цього робити, ми ж хочемо відмовитися від VCL.

Роздуми про архітектуру

Це все в загальних рисах. Якщо якісь деталі неясні - сподіваюся в реалізації стане ясніше.

Оскільки TApp-ів у нас по одному на потік - то потрібно зберігати список, у нас це буде масив, назвемо його: Applications: array of TApp; Оскільки звернення до цього масиву будуть з декількох потоків - важливо синхронізувати доступ до нього. Для цього заведемо критичну секцію: AppCS: TRTLCriticalSection;

Все це розташуємо в implementation секції модуля, щоб інші модулі не мали доступ до нашими даними.

У реалізації ж класу:

Він коректно відпрацює, і після закриття віконця наша програма завершиться.

А як же многопоточность?

Нам залишилося реалізувати простенький приклад роботи віконець в кілька потоків. Для прикладу я скористаюся класом TThread, який знаходиться в Classes.pas. Я розумію, що цей модуль вбиває в корені наш мінімалізм, але моя задача показати як зручно працюють наші віконця. Бути може, у мене знайдеться час, і я напишу подібну статтю по потокам і ООП. Отже, ось код найпростішого многопоточного додатки з двома віконцями:

Усе. Запускаємо, з'являється 2 віконця. За закриття двох віконець робота додатка закінчується.

Вище я говорив про reuse friendly модулі. На жаль, функціонал модуля дуже маленький, він як мінімум не вміє створювати дочірні віконця. Так само немає ніякої роботи з глобальними класами Windows. Ми не можемо без модифікації модуля створити навіть кнопку. Цей модуль слід розглядати лише як базовий, який показує, як можна звичайне WinAPI красиво обернути в класи, використовуючи можливості Delphi і цей модуль вже можна легко допрацьовувати до зручного повноцінного модуля. Зв'язка віконної функції з класами реалізована, а значить, моє завдання можна вважати вирішеною. Стандартна VCL Delphi діє дуже схожим чином, ось тільки з багатопоточність біля віконець VCL біда.

Я дуже постараюся незабаром викласти додаток до цієї статті. Це доповнення буде представляти із себе мій модуль і опис класів, який має набагато ширші можливості, ніж написаний тільки що "на коліні".

Спеціально для Королівства Delphi

Схожі статті