Повідомлення і їх обробка

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Повідомлення і їх обробка
Здійснення повідомлень
Ми це зробимо на прикладі натискання на клавішу. Коли ви натискаєте на клавішу, генерується апаратне переривання. Клавіатурний драйвер Windows обробляє це переривання і поміщає відповідне повідомлення в загальну чергу повідомлень Windows.
При цьому вказується, яке вікно має отримати це повідомлення. Потім Windows витягує зі своєї черги це повідомлення і поміщає його в чергу повідомлень додатку, що містить вікно-адресат. Слідом за цим вже сам додаток вибирає з черги надійшло повідомлення і передає його відповідної віконної функції.
Цей процес називається посилкою (post) повідомлень, так як посилка повідомлення нагадує посилку листа: надсилає повідомлення вказує адресата, відправляє повідомлення і більше про нього не турбується. Відправник не знає, коли точно його повідомлення отримає адресат. Процес посилки може здатися занадто складним, проте для цього існує кілька причин:
По-перше, апаратні переривання треба обробляти з усією можливою швидкістю. Тому при прийомі апаратного переривання драйвер не витрачає час на передачу повідомлення в чергу додатки, а ставить його в чергу повідомлень Windows. Апаратні переривання є асинхронними по відношенню до виконуваних додатків, а обробка повідомлень обов'язково повинна бути синхронною. Тому механізм посилки повідомлень не можна змішувати з апаратними перериваннями.
А по-друге, накопичення подій в черзі додатку допомагає зменшити кількість перемикань між додатками, так як Windows зазвичай дає додатком повністю обробити події з його черги і лише після цього перемикається на інші програми. Крім того, деякі події можуть групуватися в одне під час знаходження в черзі.

Малюнок SEQ Малюнок \ * ARABIC 1 Маршрутизація повідомлень в Windows 3.x
Витяг повідомлень з черги прикладення і направлення їх відповідним вікнам здійснює функція WinMain. Цей процес виконується в кілька прийомів:
повідомлення вибирається з черги за допомогою функції GetMessage або PeekMessage
потім повідомлення транслюється за допомогою функції TranslateMessage [0] (одне повідомлення може породжувати послідовність інших або замінюватися, як, наприклад, відбувається для повідомлень клавіатури WM_KEYDOWN). Часто трансляція складається із виклику більш ніж однієї функції, сюди можуть додаватися спеціальні засоби трансляції акселераторів і немодальний діалогів (про це пізніше).
І тільки після цього воно направляється вікна за допомогою функції DispatchMessage (це називається диспетчеризацією)
Для виконання цих операцій існують спеціальні функції. Ці функції утворять цикл обробки повідомлень, тому що після завершення обробки одного повідомлення додаток повинен приготуватися до обробки наступного. Цикл закінчується лише при завершенні роботи програми.
MSG msg;
while (GetMessage (& msg, NULL, NULL, NULL)) {
TranslateMessage (& msg);
DispatchMessage (& msg);}
Це найпростіший вид циклу обробки повідомлень. У реальних додатках він складніший. Всі три функції, що викликаються тут, належать Windows. Призначення їх повинно бути зрозуміло. Потрібно додати кілька зауважень про функції GetMessage. Ця функція має такі аргументи:
BOOL GetMessage (lpMsg, hWnd, uMsgFilterMin, uMsgFilterMax);
lpMsg вказує на структуру MSG, в яку буде записано отримане повідомлення. Якщо чергу повідомлень порожня, то GetMessage передає управління оболонці, так що та може почати обробку повідомлень іншої програми.
Які ж дані передаються повідомленням?
typedef struct tagMSG {
HWND hwnd; / / хендл вікна-одержувача
UINT message; / / номер повідомлення WM_ ...
WPARAM wParam; / / параметр повідомлення
LPARAM lParam; / / параметр повідомлення
DWORD time; / / час надходження повідомлення
POINT pt; / / координати повідомлення (для повідомлень миші)
} MSG;
Поле message структури MSG задає номер повідомлення, посланого системою. Інтерпретація параметрів повідомлення wParam і lParam залежить від самого повідомлення. Для цього треба дивитися опис конкретного повідомлення і обробляти параметри відповідним чином. Так як в системі визначено величезна кількість різних повідомлень, то для простоти використання застосовуються символічні імена повідомлень, що задаються за допомогою # define в заголовному файлі. Як приклад можна привести повідомлення WM_CREATE, WM_PAINT, WM_QUIT.
hWnd вказує хендл вікна, повідомлення для якого будуть вибиратися з черги. Якщо hWnd дорівнює NULL, то будуть вибиратися повідомлення для всіх вікон цього додатка, а якщо hWnd вказує реальне вікно, то з черги будуть вибиратися всі повідомлення, спрямовані цьому вікну або його нащадкам (дочірнім або використовуваним вікнами, або їхнім нащадкам, у тому числі віддаленим ).
uMsgFilterMin і uMsgFilterMax зазвичай встановлені в NULL. Взагалі вони задають фільтр для повідомлень. GetMessage вибирає з черги повідомлення, номери (імена) яких лежать в інтервалі від uMsgFilterMin до uMsgFilterMax. Нульові значення виключають фільтрацію.
Функція GetMessage повертає у всіх випадках, крім одного, ненульове значення, яке вказує, що цикл треба продовжувати. Тільки в одному випадку ця функція поверне 0 - якщо вона отримає з черги повідомлення WM_QUIT. Це повідомлення надсилається тільки при закінченні роботи програми.
Після завершення циклу треба зробити зовсім небагато - звільнити пам'ять від тих об'єктів, які створювалися під час роботи програми (якщо вони ще існують). Деякі об'єкти, які знищуються автоматично, можна не звільняти - це зробить Windows. Такий, наприклад, зареєстрований нами клас вікон.
І залишається ще одну справу: так як WinMain повертає результат, то ми повинні повернути будь-яке значення. У Windows прийнято, що повертається значення є параметром wParam повідомлення WM_QUIT, що завершив цикл обробки повідомлень. Таким чином ми пишемо:
return msg.wParam;
Здійснення та передача повідомлень
Раніше, в розділі "REF _Ref422032812 \ * MERGEFORMAT Помилка! Джерело посилання не знайдено. ", Ми розглядали метод передачі повідомлень, званий посилкою повідомлень (post message), та їх обробки - витягу з черги в циклі обробки повідомлень, трансляції та подальшої передачі віконної процедури. Джерелами таких повідомлень можуть бути як компоненти системи, наприклад, клавіатурний драйвер, так і сам додаток. Для посилки повідомлення в API передбачена функція
BOOL PostMessage (hWnd, uMsg, wParam, lParam);
Ця функція ставить повідомлення в чергу. Значення, що повертається TRUE указує, що повідомлення поставлено в чергу, FALSE - виникла помилка (наприклад, помилково вказаний адресат або чергу повідомлень переповнилася). Пізніше повідомлення буде вилучено з черги викликом функції GetMessage або PeekMessage в циклі обробки повідомлень.
Однак механізм посилки повідомлень не завжди зручний, тому що не дозволяє отримати результат обробки повідомлення, або дочекатися його завершення. Точніше, дозволяє, але дуже громіздким способом - треба вводити спеціальні відповідь повідомлення і чекати їх отримання.
Взагалі кажучи, процес здійснення та обробки надісланих повідомлень часто називають асинхронним способом обробки повідомлень, так як сама посилка повідомлення і його обробка ніяк між собою не зв'язані за часом.
Для вирішення цих завдань вводиться альтернативний механізм, званий передачею повідомлень (send message). При цьому повідомлення у чергу не потрапляє, а спрямовується безпосередньо віконної функції [1]. Приблизно його можна розглядати як безпосередній виклик процедури обробки повідомлень. Для передачі повідомлення використовується функція
LONG SendMessage (hWnd, uMsg, wParam, lParam);
Вона викликає віконну процедуру вказаного вікна, отримує результат обробки повідомлення і повертає керування в викликала процедуру після обробки зазначеного повідомлення. Повертане цією функцією значення збігається з результатом, виконання віконної функції. Ви можете скористатися нею для передачі тих чи інших повідомлень навіть до організації циклу обробки повідомлень, так як черга повідомлень при цьому не використовується (крім особливих випадків у Win32 API - див. нижче).
Багато функцій API використовують SendMessage для передачі повідомлень вікна. Наприклад, функція CreateWindow, створює вікно, в процесі створення передає йому повідомлення WM_CREATE (і не тільки одне це повідомлення), а результат, повернений обробником повідомлення, використовується функцією CreateWindow для продовження створення вікна.
Процес передачі повідомлень за допомогою функції SendMessage називається синхронної обробкою повідомлень, так як процес передачі та обробки повідомлень жорстко впорядкований в часі.
Те, що повідомлення, передані за допомогою функції SendMessage, минуть цикл обробки повідомлень, накладає обмеження на застосування цієї функції. Справа в тому, що цикл обробки повідомлень виконує над деякими повідомленнями певні операції. Так, наприклад, не можна передавати повідомлення WM_QUIT - воно обов'язково має пройти через чергу повідомлень, так як використовується для завершення циклу обробки повідомлень. Інші повідомлення (наприклад, клавіатури) транслюються в циклі і їх теж треба посилати, а не передавати.
Іноді повідомлення передаються не якого-небудь конкретного вікна і не будь-якій з додатком (потоку), а всіх програм, запущеним в системі. Для цього використовуються широкомовні повідомлення (broadcast message), які передаються всім головним вікнам всіх додатків. Для передачі такого повідомлення необхідно вказати в якості хендла вікна-одержувача спеціальний символ HWND_BROADCAST (рівний -1), наприклад:
SendMessage (HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM) hwndDDEClient, 0L);
У цьому випадку повідомлення WM_DDE_INITIATE буде передано головним вікнам всіх додатків, так що всі, хто працює DDE-сервера зможуть на нього відповісти.
Істотна особливість широкомовних повідомлень - те, що вони будуть оброблятися відразу великою кількістю вікон. Тобто отримати результат від обробника цього повідомлення звичайним шляхом не можна, тому що залишається невизначеність, результат якого обробника повертати. В окремому випадку (Win32 API) можна отримати відповіді від усіх обробників за допомогою функції SendMessageCallback. У загальному випадку при необхідності отримання відповіді від обробника той повинен надіслати Вам відповідь (саме так зроблено при встановленні DDE-розмови).
Особливості посилки повідомлень в Windows API
Крім розглянутих функцій у Windows API існує ще дві функції, що посилають повідомлення. З однією з них - PostQuitMessage ми вже зустрічалися в прикладі 1A. Ця функція надсилає повідомлення WM_QUIT, яке служить для завершення циклу обробки повідомлень.
BOOL PostQuitMessage (wParam);
Повідомлення WM_QUIT цікаво ще й тим, що воно не надсилається ніякому вікна - хендл вікна-одержувача дорівнює NULL. В принципі у вас є можливість самому посилати такі повідомлення своєму додатку, проте для цього звичайна функція PostMessage не підходить. Замість неї передбачена інша функція
BOOL PostAppMessage (hTask, uMsg, wParam, lParam);
Ця функція направляє повідомлення додатком, вказаним хендлов завдання hTask [2]. Для отримання цього хендла можна скористатися функціями GetCurrentTask або GetWindowTask.
При використанні функцій, що посилають повідомлення, необхідно враховувати обмеження на розмір черги повідомлень. За замовчуванням чергу здатна утримувати лише 8 повідомлень. При необхідності збільшити розмір черги треба скористатися функцією
BOOL SetMessageQueue (cMaxMsg);
де параметр cMaxMsg задає кількість повідомлень, які можуть перебувати в черзі. Це число не може бути більше 120. Функція знищує стару чергу, разом з усіма повідомленнями, які можуть в ній опинитися, і створює нову, порожню, зазначеного розміру. Якщо треба збільшити розмір черги, то це краще робити на самому початку, до створення вікон та організації циклу обробки повідомлень.
Особливості здійснення та передачі повідомлень в Win32 API
У Win32 у зв'язку з появою багатопотокових додатків змінився процес маршрутизації повідомлень у системі. Тепер черга повідомлень належить не додатком (процесу), а потоку, що створив її. Потік, що не використовує черг і, відповідно, не має циклу обробки повідомлень, називається робочим потоком (worker thread), а потік, що працює з вікнами, що створює свою чергу і цикл обробки повідомлень називається інтерфейсним потоком (interface thread). В одному додатку Win32 може виявитися кілька робочих потоків, декілька інтерфейсних потоків і, відповідно, кілька черг одночасно.
Ще одна відмінність пов'язано з тим, що розмір черзі більше не фіксований - при необхідності чергу динамічно збільшується. Завдяки цьому функція SetMessageQueue більше не застосовується.
Можливість застосування декількох інтерфейсних потоків в одному процесі призводить додатково до відмови від функції PostAppMessage - тому що залишається неясно, якому потоку треба послати повідомлення, і до додавання функції:
BOOL PostThreadMessage (dwThreadID, uMsg, wParam, lParam);
яка посилає повідомлення конкретного потоку. При цьому в чергу відповідного потоку міститься повідомлення, що має нульовий хендл вікна-одержувача. Ідентифікатор потоку dwThreadID можна отримати за допомогою функцій:
DWORD GetWindowThreadProcessId (hWnd, lpdwProcessID);
DWORD GetCurrentThreadId (void);
Так як один процес може мати кілька потоків, кожен зі своєю чергою і своїми вікнами, та повідомлення можуть передаватися (SendMessage) вікнам будь-якого потоку і навіть будь-якого процесу, то для забезпечення необхідного рівня захисту було прийнято рішення, що б повідомлення, спрямовані вікна, оброблялися тільки тим потоком, який це вікно створив. Якщо повідомлення передається вікна, створеному тим-же потоком, то функція SendMessage просто викликає віконну процедуру для обробки повідомлення.
А якщо вікно створено іншим потоком, то процес передачі повідомлення призводить до складного взаємодії потоку-відправника та потоку-одержувача. Передавальний потік поміщає повідомлення в чергу приймає потоку зі спеціальним прапором передане повідомлення (QS_SENDMESSAGE) і припиняється до одержання відповіді. Приймаючий потік, закінчивши обробку поточного повідомлення, витягує з черги першими повідомлення з прапором передане, обробляє їх, повертає результат і після цього відновлює роботу передав повідомлення потоку.
Проте в житті ситуація істотно ускладнюється: часто потік, що обробляє передане повідомлення, передає послав потоку яке-небудь «зустрічну» повідомлення. Так, наприклад, при початку DDE-розмови DDE-клієнт передає за допомогою функції SendMessage широкомовне повідомлення WM_DDE_INITIATE, а DDE-сервер відповідає на це повідомлення передачею повідомлення WM_DDE_ACK, якщо він підтримує необхідний DDE-розмова. Тобто обробник повідомлення WM_DDE_INITIATE сервера передає за допомогою тієї-ж функції SendMessage повідомлення WM_DDE_ACK вікна клієнта, яке в даний момент чекає кінця обробки переданого їм повідомлення WM_DDE_INITIATE [3]. При цьому було б можливим зависання обох потоків, так як потік клієнта зупинено до кінця обробки переданого їм WM_DDE_INITIATE, а потік сервера буде чекати, поки зупинений потік клієнта не відповість на зустрічну повідомлення WM_DDE_ACK, яке він передає в якості підтвердження.
Що б уникнути подібних неприємностей функція SendMessage може обробляти повідомлення, передані даному потоку іншими потоками, перебуваючи в очікуванні відповіді від обробника переданого повідомлення.
Обережно! Зависання потоків при обміні повідомленнями все-таки можливо. Така ситуація легко може статися при використанні MDI: у Win32 для кожного відкритого дочірнього MDI-вікна автоматично створюється свій власний потік. У цьому випадку при використанні функції SetFocus для передачі фокусу від одного дочірнього MDI-вікна іншому дочірньому MDI-вікна (природно належить іншому потоку) відбувається обмін повідомленнями WM_SETFOCUS і WM_KILLFOCUS, що призводить до зупинки обох потоків. Ця особливість платформи офіційно описана Microsoft.
Крім того можливо зависання потоку, який передав повідомлення, якщо потік-одержувач завис сам або зайнятий дуже тривалими операціями. Так як тривала зупинка передавального повідомлення потоку може бути небажана, то в Win32 API описані спеціальні функції для роботи з межпотоковимі повідомленнями: SendMessageTimeout, SendMessageCallback, SendNotifyMessage і ReplyMessage.
Увага! Функції SendMessageTimeout, SendMessageCallback і SendNotifyMessage не реалізовані для платформи Win32s а, крім того, в документації (правда не завжди) зустрічається дивне згадка про те, що у разі Windows-95 параметр wParam є 16ти двійкового (!), Що може істотно обмежити застосування цих функцій для передачі багатьох повідомлень.
LRESULT SendMessageTimeout (
hWnd, uMsg, wParam, lParam, fuFlags, uTimeout, lpdwResult);
Додаткові параметри цієї функції: lpdwResult - вказує змінну, в яку буде поміщений результат обробки повідомлення, uTimeout - максимальний час, в мілісекундах, протягом якого треба чекати результату, а fuFlags - визначає, як функція буде чекати відповіді:
прапор SMTO_ABORTIFHUNG говорить про те, що якщо потік-одержувач завис (тобто вже більше 5 секунд не обробляє ніяких повідомлень), то не чекати результату, а відразу повернути управління.
прапор SMTO_BLOCK виключає обробку «зустрічних» повідомлень під час очікування відповіді. З цим прапором треба бути дуже обережним!
прапор SMTO_NORMAL використовується тоді, коли ні SMTO_BLOCK ні SMTO_ABOPRTIFHUNG не вказані.
Повертається функцією результат дорівнює TRUE, якщо повідомлення оброблено або FALSE, якщо відведений для очікування час минув або потік-одержувач завис.
Якщо функція SendMessageTimeout застосовується для передачі повідомлення вікна, створеному тим-же самим потоком, то вона працює як звичайна функція SendMessage - час очікування не обмежено і перевірка на зависання не виконується.
Друга функція SendMessageCallback передає повідомлення вказаною вікна і повертає управління негайно після цього, не чекаючи результату обробки повідомлення. Але коли повідомлення буде оброблено, буде викликана зазначена вами функція, обробна отриманий результат.
BOOL SendMessageCallback (hWnd, uMsg, wParam, lParam, lpfnResultCallback, dwData);
void CALLBACK ResultCallback (hWnd, uMsg, dwData, lResult) {...}
Параметр lpfnResultCallback є покажчиком на функцію-обробник результату, а dwData - деякі додаткові дані, які ви хочете передати у вашу функцію-обробник. Прототип цієї функції наведено в наступному рядку.
Уточнення: реально функція-обробник результату буде викликана не негайно після обробки повідомлення одержувачем, а результат обробки буде поміщений в чергу оброблених повідомлень, звідки він буде витягнуто тоді, коли потік, який передав повідомлення, звернутися до черги повідомлень для отримання наступного (тобто при виклику GetMessage, PeekMessage, WaitMessage або одну з функцій SendMessage ...).
Функцію SendMessageCallback можна з успіхом поєднувати з широкомовними повідомленнями - тоді у вас з'являється можливість аналізувати результати обробки цього повідомлення усіма головними вікнами додатків.
Якщо функція SendMessageCallback використовується для передачі повідомлення вікна, створеному тим-же потоком, то отримання результату і виклик функції-обробника результату здійсниться негайно, до повернення з функції SendMessageCallback.
Окремо варто розглянути питання оптимізації обробки межпотокових повідомлень. Справа в тому, що Windows NT може працювати на багатопроцесорних системах, реально виконуючи кілька потоків в один і той же час. У цьому випадку може бути доцільно скоротити час, протягом якого один потік просто очікує результат обробки іншого потоку. Для цього призначені дві додаткові функції ReplyMessage і SendNotifyMessage.
Функція ReplyMessage використовується обробником межпотокового повідомлення для того, що б достроково повідомити викликав потоку про закінчення обробки повідомлення. Так, наприклад, можлива ситуація, коли потік-одержувач повідомлення з'ясовує, що повідомлення буде успішно оброблено і навіть відомо, який результат треба буде повернути на самому початку обробки, але до повного завершення необхідно виконати ще деякі додаткові дії. У цьому випадку буде зручно повідомити необхідний результат відразу після його отримання за допомогою функції ReplyMessage, після чого продовжити обробку повідомлення. Потік, який передав повідомлення зможе продовжити свою роботу не чекаючи повного завершення обробки повідомлення.
Функція SendNotifyMessage з'явилася на світ у зв'язку з тим, що багато таких повідомлень є просто інформаційними. Тобто вони повідомляють вікна, що з ним зроблено те-то і те-то, але результат обробки цього повідомлення не потрібен. Часто такі повідомлення повинні передаватися в синхронному режимі, наприклад повідомлення WM_DESTROY повинно бути оброблено під час виконання функції DestroyWindow і до обробки повідомлення WM_NCDESTROY, але те як воно буде оброблено і який результат його обробки - не важливо. Як зворотний приклад можна привести обробку повідомлення WM_CREATE, так як результат обробки потрібен для подальшого створення вікна або відмови від її створення.
Для синхронної передачі інформаційних повідомлень зручно застосовувати функцію SendNotifyMessage, яка передає повідомлення як звичайна функція SendMessage, але не чекає результату обробки цього повідомлення. До певної міри ефект від виконання функції SendNotifyMessage схожий на ефект від функції PostMessage, але передане таким способом повідомлення буде опрацьоване завідомо то того, як буде опрацьовано будь надіслане звичайним способом повідомлення.
Детальніше про цикли обробки повідомлень
Цикл обробки повідомлень, у тому найпростішому вигляді, який застосовано у прикладі 1A, застосовується рідко. У реальних додатках цей цикл ускладнюється. Зазвичай при цьому переслідують такі завдання:
включити додаткові кошти трансляції повідомлень, зазвичай вживані акселераторами і немодального діалогу;
при виконанні будь-яких тривалих дій дати можливість обробляти повідомлення (наприклад, в процесі друку документа);
включити обробку станів простою (коли в черзі немає повідомлень) для виконання будь-яких дій у фоновому режимі.
При розгляді двох останніх завдань слід мати на увазі, що аналогічного ефекту можна домогтися застосуванням багатопотокових додатків. Більше того, у багатьох випадках багатопотокових варіант виявляється кращим. При ухваленні рішення необхідно враховувати специфіку розробляється і його область застосування. Якщо відмова від застарілої 16ти розрядної платформи Windows 3.x не викликає заперечень, а також мова йде про розробку нової програми, а не перенесення старого на нову платформу, то варто серйозно опрацювати питання з реалізацією багатопотокового додатки.
Додаткова обробка повідомлень
Перше завдання - додаткова обробка повідомлень - зазвичай зводиться до виконання додаткових дій після вилучення повідомлення з черги і трансляцією цього повідомлення. Для застосування акселераторів зазвичай використовують функцію TranslateAccelerator або TranslateMDISysAccel, для немодальний діалогів - IsDialogMessage. Часто функції, які виконали специфічну трансляцію повідомлення, заодно передають їх віконної процедури, так що наступна трансляція і виклик DispatchMessage стають не потрібні. Наприклад:
HWND hwndDlg = ...; / / хендл вікна немодального діалогу
MSG msg;
while (GetMessage (& msg, NULL, NULL, NULL)) {
if (! IsWindow (hwndDlg) | |! IsDialogMessage (hwndDlg, & msg)) {
TranslateMessage (& msg);
DispatchMessage (& msg);}}
У цьому прикладі перевіряється наявність вікна немодального діалогу і, якщо воно існує, то виконується спеціальна трансляція повідомлень за допомогою функції IsDialogMessage. Повідомлення, оброблені цією функцією, в подальшій трансляції не беруть участь
Тривалі операції
Друге завдання - дати можливість нормальної роботи інтерфейсу при виконанні тривалих операцій. Це завдання зазвичай вирішується шляхом вбудовування допоміжних циклів обробки повідомлень в процесі виконання тривалих операцій. Єдине, що при цьому потрібно - що б цикл завершувався не по отриманню WM_QUIT, а по завершенню обробки останнього повідомлення в черзі, що б додаток міг продовжити свою операцію. Для цього застосовують функцію PeekMessage замість GetMessage.
BOOL PeekMessage (lpMsg, hWnd, uFilterFirst, uFilterLast, fuRemove);
Ця функція здійснює вибірку повідомлень з черги, проте, якщо чергу порожня, функція PeekMessage просто повертає FALSE, а не чекає надходження повідомлення. Її параметри аналогічні параметрам функції GetMessage, крім додаткового fuRemove. Цей параметр може бути комбінацією прапора PM_NOYIELD і одного з прапорів PM_REMOVE або PM_NOREMOVE.
Прапор PM_NOYIELD говорить функції про те, що за відсутності повідомлень в черзі не можна передавати управління іншим додаткам. Зазвичай PeekMessage за відсутності повідомлень в черзі передає управління іншим додаткам і, тільки якщо їх черги теж порожні, повертає FALSE. Таким чином цикл, побудований на PeekMessage без прапора PM_NOYIELD, не виключає можливість нормальної роботи інших додатків.
Прапор PM_NOREMOVE явно вказує, що потрібно тільки перевірити наявність повідомлень в черзі. Якщо повідомлення є, повертається інформація про це повідомлення (у структурі MSG), а повідомлення залишається в черзі. Прапор PM_REMOVE вказує на необхідність вилучення повідомлень з черги.
MSG msg;
/ / Тривалі операції (обчислення, друк ...)
while (PeekMessage (& msg, NULL, NULL, NULL, PM_REMOVE)) {
TranslateMessage (& msg);
DispatchMessage (& msg);}
/ / Подальші тривалі операції
Такий цикл спочатку обробить всі повідомлення, що накопичилися в черзі, а потім завершитися. При використанні таких циклів треба мати на увазі наступне:
Ви можете обмежити роботу інтерфейсу тільки яким-небудь одним вікном, може бути панеллю діалогу (і всіма дочірніми вікнами даного вікна). Тоді необхідно вказати другим параметром функції PeekMessage хендл цього вікна, а в циклі передбачити необхідну трансляцію для роботи цього вікна (панелі діалогу).
Ви можете дозволити нормальну роботу всього програми. Тоді ви повинні передбачити в цьому циклі таку ж саму трансляцію, як і в циклі обробки повідомлень у функції WinMain (це зручно зробити, виділивши всю необхідну трансляцію в окрему процедуру) і, додатково, передбачити можливість отримання WM_QUIT в такому циклі. Під час вилучення WM_QUIT треба або прийняти заходи до негайного завершення роботи програми, або завершити тривалі операції і послати WM_QUIT знову. Наприклад, так:
MSG msg;
BOOL fStop = FALSE;
/ / Тривалі операції (обчислення, друк ...)
while (PeekMessage (& msg, NULL, NULL, NULL, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
fStop = TRUE;
PostQuitMessage (msg.wParam); / / так завершиться цикл в WinMain
break; / / а так - цей}
MyTranslation (& msg); / / будемо вважати, що сюди ми прибрали всю
/ / Трансляцію і виклик DispatchMessage}
if (! fStop) {
/ / Подальші тривалі операції}
Обробка станів простою
Третє завдання - виконання дій у стані простою, дуже близька до попередньої. Часто все тривалі операції виконують саме в стані простою. Відмінність полягає в тому, як саме організовується виконання таких дій.
У попередньому варіанті фрагмент коду, що виконує тривалі операції, просто містить додаткові цикли обробки повідомлень. Тобто ви практично розробляєте деяку програму, що виконує тривалі обчислення, а потім не змінюючи алгоритму, вставляєте в нього додаткові цикли обробки повідомлень. При цьому невеликі складності виникають тільки з обробкою повідомлень.
Розглянутий тут варіант - обробка станів простою дозволяє виконати ті-ж операції дещо іншим способом. У циклі обробки повідомлень можна легко визначати моменти, коли всі повідомлення в черзі вже оброблені і, якщо це так, викликати обробку спеціального повідомлення (або процедури) обслуговуючого стан простою. Складнощі будуть пов'язані з тим, що сама по собі обробка стану простою не повинна бути тривалою - так як вона тимчасово зупиняє цикл обробки повідомлень. Якщо ви збираєтеся організувати виконання тривалих операцій таким способом, то ви повинні розділити їх на невеликі, швидко виконуються фрагменти, які будуть послідовно викликатися при обробці станів простою.
Для реалізації циклів з обробкою станів простою зазвичай застосовують одну з двох функцій: WaitMessage або GetMessage. Так як з WaitMessage ми ще не зустрічалися, то спочатку розглянемо варіант із неї:
void WaitMessage (void);
Ця функція просто очікує надходження в чергу нового повідомлення. Причому, навіть якщо в черзі вже є повідомлення, вона все-одно буде чекати нового, тому її викликають тільки коли черга повідомлень порожня.
for (;;) {
while (! PeekMessage (& msg, NULL, NULL, NULL, PM_REMOVE)) {
/ / ... виконати операції під час простою
WaitMessage (); / / дочекатися наступного повідомлення}
if (msg.message == WM_QUIT) break; / / перервати цикл по WM_QUIT
/ / Нормальна обробка повідомлення
TranslateMessage (& msg);
DispatchMessage (& msg);}
Такий цикл застосовують зазвичай безпосередньо у функції WinMain, замість звичайного. Треба відзначити, що виклик однієї функції GetMessage замінився на цілий цикл з PeekMessage, необхідних операцій і WaitMessage, а також перевірку повідомлення WM_QUIT. Причому вкладений цикл виконуватися одноразово для кожного стану простою - після повернення з функції WaitMessage в черзі буде присутній повідомлення, PeekMessage поверне TRUE і вкладений цикл завершитися.
У принципі, замість WaitMessage можна використовувати GetMessage, приблизно так:
for (;;) {
if (! PeekMessage (& msg, NULL, NULL, NULL, PM_REMOVE)) {
/ / ... виконати операції під час простою
if (! GetMessage (& msg, NULL, NULL, NULL)) break; / / WM_QUIT (!)}
if (msg.message == WM_QUIT) break; / / перервати цикл по WM_QUIT
...}
На чому зупинитися?
При виборі між двома різними способами організації тривалих операцій треба враховувати наступні міркування:
Варіант з обробкою станів простою істотно більш трудомісткий, однак має певні переваги, пов'язані з тим, що може бути організовано виконання декількох видів тривалих операцій одночасно. Наприклад, можна в стані простою організувати одночасну друк документа на принтері, виконання складного математичного розрахунку і, скажімо, оптимізацію використання пам'яті завданням. До певної міри така організація програми дозволяє імітувати багатопотокових завдання в рамках одного потоку. Це може бути виправдане при розробці додатків, що працюють у середовищі Windows 3.x, не підтримує багатопоточність завдання. Для додатків Win32 API зазвичай простіше скористатися додатковими потоками.
Як правило трудомісткість реалізації істотно залежить від спочатку поставленої перед розробником завдання. Якщо завдання спочатку поставлена ​​так, що виконання тривалих операцій відразу орієнтоване на обробку станів простою, то це практично не потребуватиме додаткових витрат часу і сил. Але переробка вже реалізованих у вигляді одного безперервного процесу тривалих операцій займе неабияку час і не завжди може бути зроблена без кардинальної переробки завдання.
Ретельний аналіз створюваного додатка часто призводить до спрощення поставленого завдання. Так, наприклад, можливість організувати друк документа у фоновому режимі (при обробці станів простою або в окремому потоці), мимоволі породжує бажання скористатися цією можливістю "на всю котушку" - дозволити одночасне редагування документа, його друк, а також друк ще декількох інших документів разом . Дух захоплює від можливостей, що відкриваються.
Проте задумайтеся над такими питаннями:
якщо документ і друкується, і редагується одночасно, те що треба друкувати - то, що було в документі на момент початку друку, те що в ньому вже є з урахуванням зроблених виправлень, або те, що буде, коли виправлення закінчаться?
як ви собі уявляєте одночасну друк двох документів на одному принтері?
які будуть вимоги до комп'ютера, що б він міг без істотних затримок в роботі редактора виконувати друк кількох документів одночасно?
Звичайно, у всіх випадках можна знайти виходи із ситуації, наприклад: робити копію документа, яка буде відправлена ​​до друку, що дозволить продовжити редагування, використовувати різні принтери для друку різних документів, або система сама організовує спулінг для друку декількох документів на одному принтері і так далі.
Питання в іншому - як часто ці можливості будуть використовуватися і виправдають чи себе істотно збільшені вимоги до ресурсів, якщо ці можливості будуть використовуватися вкрай рідко?
Ось хороший приклад - редактор Notepad вкрай простий, можливості його бідні, але він займає мало ресурсів, швидко завантажується сам і швидко зчитує текстові файли. Редактор WinWord 97 має незмірно великими можливостями, але й незмірно великими вимогами до ресурсів системи, довго завантажується сам і досить довго (в порівнянні з Notepad) завантажує редагований текст. Тепер питання - якщо вам треба підправити пару рядків в autoexec.bat, ви скористаєтеся редактором Notepad або WinWord?
Розробляючи новий додаток ви повинні дуже добре уявляти собі його область застосування і коло завдань, які вона повинна буде вирішувати. Сент-Екзюпері казав, що "досконалість досягається не тоді, коли вже нема чого додати, а тоді, коли нічого відняти". Перевантажуючи завдання різними, може бути і зручними, але рідко використовуваними функціями, ви ризикуєте зробити його незручним при виконанні часто виконуваних функцій і, в кінцевому підсумку, даремним - тому що важко уявити собі більш непотрібну річ, ніж та, якої ніхто не хоче користуватися.


[0] У деяких посібниках у найпростіших прикладах обходяться без трансляції взагалі. Однак це є не зовсім коректним, оскільки функція TranslateMessage розпізнає комбінацію клавіш Alt + Space як команду натискання на кнопку системного меню. Звичайно без неї додаток буде працювати, але не в повній мірі забезпечує стандартний клавіатурний інтерфейс.
[1] У разі Win32 процес більш передачі повідомлень складний і передане повідомлення може опинитися в черзі, однак оброблятися таке повідомлення не так, як звичайні повідомлення, що знаходяться в тій-же черги. Детальніше - читайте далі.
[2] хендл завдання hTask є самостійним поняттям, він не збігається з хендлов копії додатку hInstance. Детальніше про хендла завдання дивися в розділі "REF _Ref416858548 \ * MERGEFORMAT Помилка! Джерело посилання не знайдено. ".
[3] У випадку 16ти бітової платформи Windows таких проблем не виникає, тому що використання функції SendMessage подібно простому викликом віконної процедури.
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Лекція
63.6кб. | скачати


Схожі роботи:
Обробка сировини виробництво напівфабрикатів обробка овочів і грибів
Гідроабразивне обробка Обробка вибухом
Судові повідомлення і виклики
Кіно як семіотичної повідомлення
Повідомлення про погашення заборгованості
Повідомлення про Сонячну систему
Повідомлення меню програми Windows
Повідомлення-доповідь за твором ІС Тургенєва Напередодні
Завідомо неправдиве повідомлення про тероризм
© Усі права захищені
написати до нас