Реферати

Учбова допомога: Ознайомлення з додатками Windows

Іван Грозний 8. Зміст Уведення......3 1. Дитинство Івана IV......4 2. Міфи про Івана Грозному......8

Лікувально-оздоровчі вправи при захворюванні серцево-судинної системи. Пролапс митральн. АТ "МЕДИЧНИЙ УНІВЕРСИТЕТ АСТАНА" Курс фізичного виховання Реферат Тема: "Лікувально-оздоровчі вправи при захворюванні серцево-судинної системи. Пролапс митрального клапана."

Татарія в складі російського багатонаціонального централізованої держави. Політика уряду в Татарії. Колоніальний гніт. Класовий склад населення. Економіка краю. Повстання селян. Колонізація краю в XVII столітті.

Раннехристианская література. А. А. Гусейнов Серед пам'ятників раннехристианской літератури, пов'язаною з апокрифічною традицією, особливе місце займає так називана література двох шляхів, относимая до "Апостольського Переказу". Мова в ній йде про розрізнення шляху життя і шляхи смерті, що стоять кожній людині.

Розрахунок ремонту транспортних засобів. Пояснювальна записка по курсовому проекті. По дисципліні: "ЕКОНОМІКА ГАЛУЗІ" Спеціальність: "Технічне обслуговування і ремонт автомобільного транспорту"

Перше знайомство

Даний розділ називається "перше знайомство". Тут ви дійсно познайомитеся з першим додатком для Windows. Але не думайте, що це знайомство тільки з найпростішим додатком. Тут ви познайомитеся з деякими основними ідеями, закладеними в системи типу Windows, а також з їх впливом на роботу (і написання) додатків. НайПростіший додаток виявляється лише мотивом для серйозного знайомства з цілим класом систем.

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

Так, наприклад, навіть в найпростішому додатку треба здійснювати виведення у вікно. Для цього необхідно вивчити графічний інтерфейс пристроїв (GDI) і контекст пристрою. Але для цього треба вже бути знайомим з диспетчером пам'яті, з описом ресурсів додатку, з використанням стандартних діалогів, з обробкою повідомлень і багато чим іншим. При розгляді будь-якого з перерахованих пунктів довелося б посилатися на інші розділи, в тому числі на той же графічний інтерфейс.

Такі різні операційні системи

Відразу обмовимося - в Windows можливо запускати додатки (application) двох різних типів - додатки Windows і додатки MS-DOS. Методи розділення ресурсів, вживані цими додатками істотно розрізнюються, як розрізнюються і методи доступу до ресурсів. У цій ситуації ми будемо, говорячи про додаток взагалі, мати на увазі додаток Windows. Якщо розмова зайде про додатки MS-DOS, то це буде обумовлене окремо.

Розглядаючи роботу додатку в середовищі Windows треба відштовхуватися від того факту, що Windows є багатозадачною середою. У цьому випадку в системі може виконуватися одночасно [1] декілька різних додатків. Кожний додаток для своєї роботи вимагає деяких ресурсів системи - дискового простору, оперативної пам'яті, часу процесора, пристроїв введення і виведення і пр. Відповідно Windows повинен виконувати функції арбітра, що здійснює розділення ресурсів між додатками і контролюючого коректність роботи додатків з виділеними ним ресурсами.

З цієї точки зору можна розглянути розвиток операційних систем, починаючи від найпростіших (типу MS-DOS) і закінчуючи досить складними (як Windows NT, Unix, OpenVMS), для того що б краще зрозуміти можливості і обмеження різних реалізацій Windows.

НайПростіші однопользовательские однозадачние операційні системи є, одночасно, примітивними арбітрами. Операційна система надає набір коштів для виконання тих або інакших операцій по роботі з ресурсами і організує запуск додатків. При цьому додатку передається управління і система чекає, поки воно завершить роботу. Під час роботи цей додаток може звертатися до системи для отримання ресурсів, а може це робити і в обхід системи. Коректність роботи додатку ніяк не контролюється - система тільки лише надає примітивний, часто неоптимальний і необов'язковий метод доступу до ресурсу. Додаток користується всіма ресурсами практично без обмежень (за винятком тих, які зайняті самою системою) і має безпосередній доступ до апаратури комп'ютера.

Такий підхід типовий для операційних систем невеликих комп'ютерів: порівняно слаба підтримка периферійних пристроїв, проста файлова система і унікальна відвертість, майже вседозволеність для додатків - оскільки конфліктувати їм не з ким. Яскравий приклад - MS DOS перших версій.

Практично такі операційні середи мало застосовні і, якщо система виявилася вдалою, то вона починає розвиватися у бік підтримки багатозадачного режиму. Більше за те - обробка переривань в однозадачной системі - неминучий "багатозадачний мінімум". Резидентна програма в однозадачной середовищі - ще одна спроба наближення до багатозадачності. Ті, хто намагався створити скільки-небудь складну резидентну програму обов'язково стикався з труднощами розділення ресурсів, оскільки ефективних механізмів для цього однозадачние ОС не містять. А завдяки своїй відвертості такі системи виявилися благодатним грунтом для розвитку вірусів.

Найбільш могутніми представляються багатозадачні, многопользовательские ОС, особливо в мультипроцессорних системах. Робота такої системи заснована на паралельному виконанні декількох додатків. Додаток може виконується або на окремому процесорі, або в режимі розділення часу - в певні тимчасові інтервали.

Додаток не знає, і практично не спостерігає присутності і виконання інших додатків. Всі проблеми безконфліктного розділення ресурсів між різними задачами лежать на системі. Додаток не знає, в який момент часу її виконання буде перерване, не знає коли буде відновлено і взагалі не реєструє факту переривання. Для однієї задачі процес виконання представляється безперервним.

Многопользовательские багатозадачні системи звичайно зустрічаються на більш продуктивних комп'ютерах, при цьому вони містять могутні кошти взаємодії з периферією, що практично виключають необхідність доступу додатків безпосередньо до апаратури. Крім того операційна система має дуже гнучку і ефективну систему розподілу ресурсів, привілеїв і меж доступу до всіх ресурсів комп'ютера, а також містить кошти захисту від несанкціонованого доступу до них. Як приклади можна привести OpenVMS, Unix, в дещо меншій мірі Windows NT (реалізована для персонального комп'ютера).

Однак такі зручні і могутні ОС часто не доцільно реалізовувати на персональному комп'ютері для якого (по ідеї його використання) не потрібна многопользовательская система, і тому його відвертість далеко не завжди є уразливістю, але часто навіть підвищує ефективність його використання. Зрозуміло також, що із зростанням потужності персонального комп'ютера, з'являється необхідність створення простих багатозадачних ОС, орієнтованих на роботу одного користувача.

Така система займає проміжне положення між однозадачной і многопользовательской багатозадачної системами. Очевидно, що її ядро буде володіти більшою відвертістю, більшою можливістю доступу до апаратури і відносно простими методами розділення ресурсів, чим в складних системах.

Як ілюстрації до цього можна привести Windows 3.x, розділення ресурсів в якій в значній мірі засноване на методах MS-DOS, а також Windows-95, яка займає проміжне положення між Windows NT і Windows 3.x, надаючи істотно спрощені методи доступу до ресурсів, чим Windows NT і, в той же час, забезпечуючи якісно кращий захист, ніж Windows 3.x.

Короткі відомості про розділення ресурсів в Windows

Для того, що б Windows міг успішно розділяти ресурси, на програми, що розробляються накладають ряд вимог [2]. Зрозуміло, що першою такою вимогою є вимога не використання апаратури безпосередньо. Ви можете використати тільки кошти Windows для виконання всіх своїх задач, але не здійснювати доступ до апаратури безпосередньо.

Наприклад, додаток не повинен звертатися до відеопам'яті, коштів BIOS і т. д. Якщо додаток повинно вивести на дисплей яке-небудь зображення воно зобов'язано скористатися існуючими функціями Windows.

Звісно, обмеження на доступ до апаратури - не саме приємне обмеження, але воно має і свою оборотну, причому приємну, сторону: користувач (і розробник) може не думати про те, з чим конкретно він має справу - про це піклуватися Windows. Тобто Windows сам забезпечує виконання всіх необхідних операцій на обладнанні, що є, причому використовуючи практично всі можливості цього обладнання.

Цікаво оглядово розглянути методи розділення основних ресурсів комп'ютера між задачами, прийнятими в Windows, і вплив цих методів на правила написання програм. Причому для першого знайомства відштовхуватися ми будемо від найбільш простої системи - Windows 3.x.

До основних ресурсів, що розділяються безсумнівно відносяться дисплей, клавіатура, миша, оперативна пам'ять, процесор і диск. Варто коротко відмітити методи розділення кожного з цих ресурсів.

Дисплей

Для розділення дисплея між різними задачами в Windows використовуються вікна (window). Кожній задачі призначено, щонайменше, одне вікно, і здійснювати висновок додаток може (точніше повинне) тільки в це вікно.

Додаток може володіти декількома вікнами. У цьому випадку, звичайно, одне вікно є батьківським (parent), а інші є дочірніми (child) вікнами по відношенню до батьківського вікна. Як правило, додаток має тільки одне вікно, що не має батьків - це так зване головне вікно додатку (main window). Всі інші вікна додатку є дочірніми по відношенню до цього вікна.

Вікна можуть переміщатися по екрану, перекриваючи повністю або частково інші вікна. Вікна можуть знаходитися в максимізованому ( "розкритому" на весь екран, maximized, zoomed), нормальному або мінімізованому (minimized, iconed) стані. У мінімізованому стані вікно замінюється на спеціальну невелику картинку, звану піктограмою (іконою, icon), або вміщується в спеціальний список вікон (taskbar або systray для Windows-95 і Windows NT 4.0).

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

Те, що не вся робота по перемальовуванню вікон, що перекриваються виконується системою, пов'язане з використанням графічного режиму відображення вікон. Для повної автоматизації необхідно було б "виртуализовать" всю роботу з вікнами - тобто в звичайній оперативній пам'яті повинна знаходитися копія зображення вікна. Тоді Windows міг би повністю або частково відновлювати зображення при появі раніше невидимої частини вікна по цій копії. Однак загальний розмір декількох копій (для кожного вікна своя копія) може бути порівнянний з об'ємом всієї оперативної пам'яті комп'ютера. Скажемо для режиму 1280x1024, 16 біт/пікселя (це далеко не самий хороший) битмап екрана займає приблизно 2.5 MB. Крім того, розмір вікна може бути більше екрана і таких вікон може бути декілька. Таким чином Windows практично не може використати віртуальні вікна - ресурсів комп'ютера для цього явно не вистачає (їх ще треба розділяти з додатками, що виконуються і з компонентами самої системи).

Суворо говорячи, вікно в Windows є тим самим об'єктом, для якого частково реалізований об'єктно-орієнтований підхід. Цікаво, що в документації Windows термін "об'єкт" ніколи не застосовується до вікна, а те, що називається "об'єктами", ні в якій мірі не є об'єктами ООП.

Клавіатура і миша

Ці пристрої відносяться до пристроїв введення даних. Розділення цих пристроїв між різними задачами легко можна розбити на два кола питань:

- визначення, до якої задачі відносяться дані, отримані від пристрою введення.

- передача отриманих від пристрою даних необхідній задачі.

Для миші визначення, до якої задачі відносяться дані, цілком очевидно: оскільки кожна задача має принаймні одне вікно, то інформація від миші повинна використовуватися вікном, на фоні якого знаходиться курсор миші.

Для клавіатури справа йде дещо складніше: нам доведеться ввести поняття активне вікно (active window). У даний момент часу обов'язково існує тільки одне активне вікно, це вікно виділяється кольором заголовка, рамки або підписів (якщо вікно мінімізоване). Активне вікно є користувачем клавіатури в даний момент часу. Для того, що б інша задача могла отримувати дані, необхідно зробити активним вікно, належне цій задачі.

Передача отриманих від пристрою даних необхідній задачі. Хоч інтуїтивно здається, що тут не повинно бути особливих складностей, але саме тут дуже яскраво виявляються особливості організації Windows. Ми до цього повернемося, розглядаючи метод розділення процесора.

Диск

Для розділення дискового простору використовується файлова система. Тут Windows 3.x просто користується що вже є - файловою системою DOS; Windows-95 використовує злегка модернизированную файлову систему DOS (підтримуються імена файлів довжиною до 256 символів і можливо використання так званого FAT32 замість FAT16 або FAT12). І тільки Windows NT надає власну файлову систему - NTFS, хоч може працювати і з FAT. NTFS відрізняється від FAT істотно більш складною організацією, що дозволяє створювати єдині томи з декількох дисків, організовувати дзеркальні томи або томи з надмірністю для зберігання важливих даних, а також задавати права доступу до окремих файлів конкретним користувачам. Природно, більш складна система виявляється більш чутливою до збоїв (незважаючи на спеціально прийняті заходи) і менш продуктивною (незважаючи на спеціальну оптимізацію).

Для доступу до файлів Windows надає свої власні функції. У випадку Windows 3.x ці функції в основному відповідають функціям DOS для доступу до файлів і розділення доступу. Для нормальної роботи Windows треба встановлювати програму SHARE.EXE до запуску Windows 3.1, або, у випадку Windows 3.11, буде використаний спеціальний компонент Windows - VSHARE.386. Більш того до версії Windows 3.0 включно, мав місце цікавий нюанс: Windows мав власну функцію для відкриття файлів (OpenFile), але абсолютно не надавав коштів для читання/запису - вони були просто не декларировани, хоч всередині самого Windows містилися. Програмісту рекомендувалося або застосовувати функції Run-Time бібліотеки прийнятої мови (що можна було робити лише обмежено), або написати свої процедури на асемблері. Або, що робилося набагато частіше, використати не декларированние функції Windows для роботи з файлами. Відтоді Microsoft просто декларував ці функції.

Для додатків, працюючих в Win32 про функції DOS треба просто забути - Win32 надає більш багатий набір операцій над файлами, підтримує роботу з різними файловими системами [3] а, крім того, виключає можливість застосування переривань DOS.

Пам'ять

Реалізація методів розділення пам'яті в Windows API і Win32 API якісно розрізнюються. Для цього доведеться розглянути історію розвитку диспетчера пам'яті, що буде зроблено пізніше. Зараз треба звернути увагу тільки на деякі загальні ідеї розділення пам'яті.

У обох API пам'ять ділиться на окремі блоки. Однак ділення здійснюється абсолютно різними методами.

Windows API

Коротко можна відмітити, що вся доступна для Windows пам'ять називається глобальної (іноді глобальний хип, глобальна купа, global heap). Ця глобальна пам'ять ділиться на окремі блоки, які можуть бути переміщуваними в пам'яті. У вигляді блоків глобальної пам'яті в Windows представляються навіть програми - в цьому випадку кожному програмному сегменту відповідає один блок глобальної пам'яті.

Сегмент даних програми, представлений у вигляді блоку глобальної пам'яті, може містити свою локальну купу (локальний хип, local heap). Ця пам'ять також може ділитися на блоки, званими локальними. Термін локальний застосовується до пам'яті, якщо вона належить сегменту даних програми.

Windows надає програмні засоби для маніпуляції з блоками обох видів - і глобальними, і локальними. Кожний блок може бути фіксованим, переміщуваним або що видаляється в/з пам'яті. Це забезпечує можливість як виділення великих безперервних блоків даних (за рахунок переміщення інших блоків), так і можливість видалення частини блоків при нестачі пам'яті.

Win32 API

В Windows-95 і в Windows NT використовується так звана віртуальна пам'ять. Для кожного запущеного додатку виділяється власний адресний простір, розміром 4Г, яким додаток володіє монопольно. У цьому просторі не знаходиться ніяких даних або коду інших додатків. Таким чином додатки Win32 ізольовані один від одного. Необхідно врахувати, що "адресний простір" не відповідає пам'яті, що реально виділяється - це той діапазон адрес, в якому може розміщуватися пам'ять, реально виділена додатку. Очевидно, що з можливих 4Г адресного простору використовуються звичайно тільки трохи мегабайт, займані кодом і даними додатку і необхідними компонентами системи.

Адресний простір додатку ділиться на окремі фрагменти, вмісні код, дані, службову інформацію і пр., необхідний для цього додатку. Однак таке ділення статично - переміщення фрагментів в адресному просторі не відбувається. Оптимізація доступу до пам'яті здійснюється не за допомогою переміщення блоків або їх видалення, а за допомогою механізму відображення віртуального адресного простору на фізично доступний спомин комп'ютера (спрощено можна вважати, що віртуальний адресний простір додатку - це спеціальний файл подкачки сторінок; оперативна пам'ять при цьому виконує роль кеша, в якому знаходяться ті, що тільки активно використовуються код і дані).

Крім цього в адресному просторі додатку можуть виділятися одна або декілька куп (хипов), що розділяються на окремі блоки. Ось ети-то блоки можуть переміщатися всередині своєї купи і навіть видалятися з пам'яті. Сама купа в адресному просторі додатку переміщатися не буде. Для кожного додатку виділяється щонайменше одна купа, звана стандартною (default heap). Всі функції Windows API, працюючі з глобальною або локальною купами перенесені в Win32 API так, що вони працюють саме з цією стандартною купою. При цьому немає ніякої різниці між глобальною і локальною купами.

Процесор

Вище, при розгляді різних типів операційних систем, було виділено два "чистих" типи систем: однопользовательские однозадачние і многопользовательские багатозадачні. Windows у всіх його версіях займає деякі проміжні положення між двома цими крайніми типами. Так версії Windows 3.x наближаються до найпростішого типу однопользовательских однозадачних систем (з дуже обмеженою реалізацією деяких можливостей як многопользовательской роботи, так і багатозадачного режиму), а найбільш складна Windows NT є істинно багатозадачною системою з розвиненими коштами розділення доступу користувачів.

Windows API і об'єктно-орієнтоване програмування

Методи розділення процесора, вживані різними реалізаціями Windows, цікаво розглянути в їх розвитку - від простого до складного. Так в ідеальному однозадачной середовищі, додаток, раз почавшись, виконується без перерв до повного завершення. У істинно багатозадачному середовищі додаток виконується за багато кроків, причому сам додаток не знає, коли його перервуть для обробки інших додатків - цим відає тільки система [4].

Проміжним рішенням є середа, що отримала назву псевдобагатозадачної (невитесняющая багатозадачність, non-preemptive multitasking). У такому середовищі, подібно однозадачной, система не перериває виконання додатку. Однак сам додаток повинен бути розділений на невеликі, фрагменти, що швидко виконуються. Після виконання такого фрагмента система може перейти до виконання іншого додатку. При цьому додаток сам повідомляє систему, де її можна перервати для виконання інших задач.

У Windows 3.x це може бути реалізоване двома різними методами:

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

- можна скористатися спеціальною функцією, що передає управління системі, і що повертає його назад додатку після обробки інших додатків. Таких функцій в Windows 3.x дві - Yield і DirectYield. Однак цей шлях використовується в дуже спеціальних випадках, наприклад при розробці відладчиків, через досить жорсткі обмеження на застосування цих функцій.

При написанні нормальних додатків для Windows 3.x разбиение програми на окремі функції проводиться не механічно, скажемо через 100 рядків, а функціонально - кожна функція виконує певні дії. При цьому система, викликаючи відповідну функцію, передає їй деякі дані, які вказують, що треба зробити.

Це дуже важливий момент.

Досі всі програми складалися з алгоритму, керуючого даними. На практиці це означало, що алгоритм, що описує програму, передбачав коли, де і в якій мірі можливе отримання даних і керуючих впливів, і як і куди направляти виведення.

Наприклад, при необхідності введення даних з клавіатури, програма включала в себе виклик до операційної системи (або BIOS, на худий кінець), який і повертав необхідні дані.

Ще раз: звичайна програма генерує виклики до операційної середи для отримання і виведення: алгоритм управляє даними

В розглянутому нами випадку виходить абсолютно інакша ситуація: що поступають від системи дані управляють поведінкою програми. Часто такими даними є керуючі впливи користувача (наприклад, зміна розмірів вікна, виклик меню і інш.). Ці впливи, взагалі говорячи, не синхронні з роботою вашої програми, тобто виходить, що дані управляють алгоритмом - один з основних принципів об'єктно-орієнтованого програмування (ООП).

Введемо нові поняття:

- дані, ті, що передаються від системи до відповідної функції називаються повідомленням (message).

- процес звернення до необхідної функції називається посилкою (post) або передачею (send) повідомлення.

- функція, що обробляє повідомлення, називається процедурою обробки повідомлень (message handler).

Таким чином, коли ви створюєте програму, працюючу в псевдобагатозадачному середовищі (тут: Windows 3.x), ви повинні написати необхідні процедури обробки повідомлень. Далі Windows буде передавати вашим процедурам повідомлення для їх обробки.

З точки зору ООП всі об'єкти повинні володіти 3мя властивостями:

інкапсуляція - об'єднання в єдине ціле алгоритмів і необхідних даних;

успадкування - можливість породження нових об'єктів, засновуючись на існуючих, успадковуючи їх властивості;

поліморфізм - різниця реакцій на однакові впливи; спадкоємці одного об'єкта можуть відрізнятися своїми властивостями один від одного і від предка.

З точки зору цих властивостей об'єкт, визначений процедурою обробки повідомлень, задовольняє всім цим вимогам. Процедура обробки повідомлень може користуватися специфічними, згрупованими в яких-небудь структурах, даними (інкапсуляція). Ми можемо створювати новий об'єкт зі своєю процедурою обробки повідомлень, яка може посилатися на процедуру раніше описаного об'єкта (успадкування), а також виконувати обробку додаткових повідомлень або інакше обробляти колишні повідомлення (поліморфізм).

Звичайно говорять, що процедура обробки повідомлень визначає властивості об'єкта, оскільки задає реакцію цього об'єкта на впливи (повідомлення). Саме з таким трактуванням об'єкта виникли перші мови ООП.

У Windows об'єктом ООП є вікно. Відповідно говорять, що повідомлення направлені не процедурі, а вікну. Процедура обробки повідомлень визначає вікно з конкретними властивостями, навіть більше - одна процедура може обслуговувати декілька різних вікон, але тоді ці вікна будуть мати однакову реакцію на однакові впливи. Тобто процедура обробки повідомлень визначає не одне вікно, а цілий клас (class) вікон.

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

Таким чином псевдобагатозадачний метод розділення процесора виявився основою для побудови об'єктно-орієнтованої середи і попутно перевернув всю звичну нам філософію написання програм - ми тепер створюємо не керуючий алгоритм, а набір процедур, що забезпечує реакцію нашого вікна (тобто нашої програми) на зовнішні події.

Обробка повідомлень є дуже поширеним способом організації ООП-бібліотек або ООП-мов. Істотна відмінність (причому не в кращу сторону) Windows 3.x полягає в тому, що обробка повідомлень є методом розділення процесора в псевдобагатозадачному середовищі. Оскільки система не перериває виконання додатку в процесі обробки повідомлення, то його обробка не повинна займати багато часу

Це сильно утрудняє застосування Windows 3.x для розрахункових задач - або ми повинні їх виконати швидко, або розбити на частини, що швидко виконуються, і потроху обробляти по мірі отримання повідомлень. Зрозуміло, що звичайно доводиться розбивати на частині, а це істотно вповільнює обчислення. Взагалі говорячи, при обробці повідомлення краще укладатися в інтервал менш 1 секунди, що б затримка в реакції Windows на керуючі впливи не була дуже великою; критичной є затримка порядку 1-2 хвилини - при цьому Windows 3.x може просто дати збій або зависнути (що дуже сильно залежить від наявності інших працюючих додатків).

Win32 API

У більш складному Win32 API застосовується так звана істинна багатозадачність (що витісняє, preemptive multitasking). У цьому випадку розділення процесора здійснюється по певних тимчасових інтервалах (квантам часу). Обробка повідомлень перестала бути методом розділення процесора, і в процесі обробки повідомлення система може передавати управління іншим додаткам. Сама ж ідея застосування об'єктно-орієнтованого підходу до вікон залишилася незмінною.

Однак треба відмітити, що реалізація істинної багатозадачності виявилася неповною. У рамках Win32 API можуть працювати як ці Win32 додатки, так і їх 16ти розрядні побратими, написані для Windows API. При запуску таких 16ти розрядних додатків під Win32 для них запускається спеціальна віртуальна 16ти розрядна Windows-машина, причому в Windows-95 для всіх 16ти розрядних додатків використовується одна загальна віртуальна машина. Це означає, що істинна багатозадачність реалізована тільки між Win32 додатками, в той час як 16ти розрядні додатки між собою використовують обробку повідомлень для розділення відведеного ним процесорного часу. У випадку Windows NT для кожного 16ти розрядного додатку запускається власна Windows-машина, що дозволяє їм розділяти процесор загальним способом з додатками Win32.

Істинна багатозадачність в Win32 дозволила реалізувати так звані многопотоковие додатки (multithread application). При цьому виділяють два нових поняття - процес (proccess) і потік (thread). Процеси в Win32 API приблизно еквівалентні додаткам в Windows API. Для кожного процесу виділяються певні системні ресурси - адресний простір, пріоритети і права доступу до ресурсів, що розділяються і інше, але не процесорний час. Процес тільки лише описує запущену задачу, як вона є, без безпосередніх обчислень. Для розділення процесора використовуються не процеси, а потоки, яким і виділяється процесорний час. У рамках кожного процесу виділяється свій потік, званий первинним (primary thread), що створюється за умовчанням при створенні процесу. При необхідності в межах одного процесу може бути створено багато потоків, конкуруючих між собою (і з потоками інших процесів) за процесорний час, але не за адресний простір.

Як написати додаток для Windows

Поки ми розглянули тільки основну ідею використання повідомлень для реалізації об'єктно-орієнтованої операційної середи. Зараз треба перейти до особливостей організації додатку, працюючого в такому середовищі.

Кожний додаток відкриває щонайменше одне вікно (в принципі можуть існувати додатки взагалі без вікон, але як невеликі спеціалізовані процедури, що не вимагають ніякого управління). Властивості вікна визначаються процедурою обробки повідомлень цього вікна. Таким чином, що б визначити властивості потрібного вікна, треба написати процедуру обробки повідомлень, що посилаються цьому вікну (віконну процедуру або віконну функцію - window procedure, вона ж процедура обробки повідомлень, message handler).

Одна процедура може обслуговувати повідомлення, що посилаються різним вікнам з однаковими властивостями. Говорять, що вікна, що мають одну і ту ж віконну функцію, належать до одного класу вікон. Ви повинні цю процедуру зареєструвати - це називається реєстрацією класу вікон.

Далі необхідно передбачити кошти для створення і відображення вікна зареєстрованого класу. З таким вікном користувач буде працювати - пересувати його по екрану, змінювати розміри, вводити текст і т. д. Вам необхідно забезпечити реакцію цього вікна (тобто вашого додатку) на дії користувача. Фактично ви повинні запустити механізм, що забезпечує доставку повідомлень, адресованих вашому вікну, до одержувача - віконної процедури. Цей механізм повинен працювати, поки працює ваш додаток. Такий механізм називається циклом обробки повідомлень (message loop).

Таким чином ви повинні виконати декілька кроків для створення власного додатку:

- написати віконну функцію;

- зареєструвати цю функцію (клас) в Windows, привласнивши класу унікальне ім'я;

- створити вікно, належне даному класу;

- забезпечити роботу додатку, організувавши цикл обробки повідомлень.

Трохи детальніше розглянемо, що відбувається з додатком за час його "життя" - від запуску до завершення - перед тим, як перейдемо до розгляду конкретного прикладу.

Коли ви запускаєте додатки для Windows, система спочатку знаходить файл, що виконується і завантажує його в пам'ять. Після цього додаток здійснює ініціалізацію необхідних об'єктів, реєструє необхідні йому віконні класи, створює потрібні вікна. можна вважати, що, починаючи з цього моменту, додаток здатний нормально взаємодіяти з користувачем і необхідним образом реагувати на його дії. У цей час повинен працювати цикл обробки повідомлень, який буде розподіляти поступаючі повідомлення конкретним вікнам.

Повідомлення, які буде отримувати вікно, інформують додаток про всі дії, які робить користувач при роботі з даним вікном. Так, існують повідомлення, що інформують про створення вікна, зміні його положення, розмірів, введенні тексту, переміщенні курсора миші через область вікна, виборі пунктів меню, закритті вікна і т. д. Для зручності роботи системи всі повідомлення мають унікальні номери, по яких визначається призначення цього повідомлення; а для зручності розробки прикладних застосувань для всіх повідомлень визначаються символічні назви. Наприклад:

#define WM_MOVE 0x0003

#define WM_SIZE 0x0005

В більшості випадків назви повідомлень починаються на WM_, однак назви деяких повідомлень мають префікси BM_, EM_, LBM_, CBM_ і інші.

Для початку виділимо чотири повідомлення, з якими ми будемо знайомиться першими. Це повідомлення застосовуються при створенні вікна (WM_CREATE), при закритті [5] (WM_DESTROY і WM_QUIT) і при його перемалюванні (WM_PAINT).

У той момент, коли додаток створює нове вікно, віконна процедура отримує спеціальне повідомлення WM_CREATE, що інформує вікно про його створення. При цьому вікно створюється за допомогою виклику спеціальної функції (CreateWindow, CreateWindowEx і деякі інші), яка виконує всі необхідні дії; повідомлення при цьому має лише "інформаційний" характер - воно інформує вікно про те, що його створюють. Однак реальне створення відбувається не в обробникові цього повідомлення, а в тій функції, яку викликали для створення вікна.

На повідомленні перемальовування вікна WM_PAINT треба зупинитися трохи детальніше. Справа в тому, що яка-небудь частина вікна може бути прихована від користувача (наприклад, перекрита іншим вікном). Далі в процесі роботи ця частина може стати видимою, наприклад внаслідок переміщення інших вікон. Сама система при цьому не знає, що повинне бути намальовано в цій, раніше невидимій частині вікна. У цій ситуації додаток вимушено потурбуватися про перемальовування потрібної частини вікна самостійно, для чого йому і посилається це повідомлення кожний раз, як видима область вікна змінюється.

Коли вікно закривається, воно отримує повідомлення WM_DESTROY, що інформує про закриття вікна. Як і у разі створення, повідомлення про закриття є інформаційним; реальне закриття здійснюється спеціальною функцією (звичайне DestroyWindow), яка, серед іншого, і сповістить вікно про його знищення.

Весь час, поки користувач працює з додатком, працює цикл обробки повідомлень цього додатку, що забезпечує доставку повідомлень вікнам. У кінці роботи додатку цей цикл, очевидно, повинен завершитися. У принципі, можна зробити так, що б в цикле перевірялася наявність вікон у додатку. При закритті всіх вікон цикл також повинен завершити свою роботу. Однак можна дещо спростити задачу - і в Windows саме так і зроблено - замість перевірки наявності вікон можна передбачити спеціальний метод завершення циклу при отриманні останнім вікном (звичайне це головне вікно додатку) повідомлення про його знищення (WM_DESTROY). Для цього застосовується спеціальне повідомлення WM_QUIT, яке посилається не якому-небудь вікну, а всьому додатку загалом. При видобуванні цього повідомлення з черги цикл обробки повідомлень завершується. Для посилки такого повідомлення передбачена спеціальна функція - PostQuitMessage.

Після завершення циклу обробки повідомлень додаток знищує непотрібні об'єкти, що залишилися і повертає управління операційній системі.

Зараз як приклад ми розглянемо найпростіший додаток для Windows, традиційну програму "Hello, world!". Після цього детальніше розглянемо, як цей додаток влаштований. Тут же можна помітити, що при створенні практично будь-яких, написаних на "C", додатків для Windows цей текст може використовуватися як шаблон.

Приклад 1A - перший додаток

Файл 1a.cpp

#"define STRICT

#include < windows.h >

#define UNUSED_ARG(arg) (arg)=(arg)

static char szWndClass[] = test window";

LRESULT WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

UNUSED_ARG(wParam);

UNUSED_ARG(lParam);

PAINTSTRUCT ps;

switch (uMsg) {

case WM_CREATE:

return 0L;

case WM_PAINT:

BeginPaint(hWnd, &ps);

TextOut(ps.hdc, 0, 0, "Hello, world!, "13);

EndPaint(hWnd, &ps);

return 0L;

case WM_DESTROY:

PostQuitMessage(0);

return 0L;

default:

break;

}

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

static BOOL init_instance(HINSTANCE hInstance)

{

WNDCLASS wc;

wc.style = 0L;

wc.lpfnWndProc = WinProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szWndClass;

return RegisterClass(&wc) == NULL? FALSE: TRUE;

}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

UNUSED_ARG(lpszCmdLine);

MSGmsg;

HWNDhWnd;

if (!hPrevInst) {

if (!init_instance(hInst)) return 1;

}

hWnd= CreateWindow(WndClass, "window header", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInst, NULL

);

if (!hWnd) return 1;

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

while (GetMessage(&msg, NULL, NULL, NULL)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

Рісунок1. Додаток 1a.cpp всреде Windows 3.x або Windows NT 3.x (зліва) иливсреде Windows-95 або Windows NT 4.0 (праворуч).

У залежності від платформи, на якій запускається цей додаток, зовнішній вигляд вікна може дещо змінюватися. Це пов'язано з інтерфейсом користувача, що змінився при переході від Windows 3.x і Windows NT 3.x до Windows-95 і Windows NT 4.0.

Далі ми розглянемо початковий текст більш детально. При першому погляді на нього звертають на себе увагу відразу декілька незвичайних (в порівнянні з програмами для DOS) речей:

- нові типи даних

- дивні імена змінних

- велика кількість функцій, що використовуються і параметрів,

що передаються їм Приблизно в такому порядку ми і розглянемо ці питання.

Нові типи даних

Отже, ще раз розглянемо перший Windows-додаток (1a.cpp).

Звичайно на початку "С"-програми вміщується директива препроцесора #include для включення файла, вмісного основні визначення і прототипи функцій. При написанні Windows-додатків ви повинні включити файл WINDOWS.H. Цей файл містить визначення типів, констант і функцій, що використовуються в Windows [6].

У додатку перед включенням WINDOWS.H визначається спеціальний символ STRICT:

#define STRICT

#include < windows.h >

Він вказує, що необхідно здійснювати сувору перевірку типів. Тобто використання замість змінної одного типу змінної якого-небудь іншого, навіть схожого, типу буде розглядатися компілятором як помилка.

Для більшої частини звичайних типів Windows пропонує свої власні визначення - що пояснюється можливістю реалізації на різних обчислювальних платформах і, відповідно, Windows-додатки повинні бути переносимими хоч би на рівні початкового тексту.

Для 16ти і 32х розрядних платформ істотно розрізнюються режими адресації. Наприклад, для 32х розрядних машин практично не застосовуються near і far модифікатори адреси (Win32 вимагає, що б додатки розроблялися в 32х розрядній flat-моделі пам'яті, де на все про все відводиться один 32х розрядний сегмент, розміром до 4Г). Крім того, стандартом З передбачається, що тип даних int має довжину одне слово. Тобто для 16ти розрядних машин він співпадає з типом short int, а для 32х розрядних з типом long int. Це приводить до часткової непереносимості З-програм з однієї платформи на іншу.

З великої кількості визначуваних типів виділимо декілька, з якими нам доведеться зіткнутися на самому початку. Ті, які ми будемо вводити пізніше, будуть пояснюватися по мірі надходження.

Нова назва

Значення для Windows API

Значення для Win32 API

Символи (#define)

FAR

NEAR

PASCAL

LONG

VOID

NULL

WINAPI

CALLBACK

Типи (typedef)

BOOL

BYTE

WORD

DWORD

UINT

NPSTR

PSTR

LPSTR

LPCSTR

WPARAM

LPARAM

LRESULT

FARPROC

HANDLE

HFILE

HWND

HINSTANCE

HDC

Практично для всіх певних типів існують типи "покажчик на...". Ближні покажчики будуються за допомогою префікса NP, а дальні - LP, покажчики, відповідні прийнятій моделі пам'яті, будуються за допомогою префікса P. Напрімер, BYTE - тип, що представляє окремий байт, LPBYTE - дальній покажчик на байт, а NPBYTE - ближній покажчик. Виключення - тип VOID, він має тільки дальній покажчик LPVOID.

Уважніше розберемося з типом HANDLE (і з всіма "похідними" від нього): Справа в тому, що Windows створює спеціальні структури даних, що описують необхідні об'єкти (наприклад вікно). Ця структура даних часто належить не вашому додатку, а самій системі. Для того, що б цей об'єкт можна було ідентифікувати, вводиться спеціальне поняття хендл (дескриптор, handle). Хендл в Windows - це просто ціле число, іноді номер, привласнений даному об'єкту, причому значення NULL вказує на неіснуючий об'єкт. Єдине виключення - HFILE, для якого визначене спеціальне значення - HFILE_ERROR, рівне -1 (це пов'язано з тим, що хендл файла спочатку був запозичений у DOS, де хендл 0 означає стандартний пристрій висновку stdout). Поняття хендла в Windows використовується дуже широко, а для полегшення контролю типів використовується велика кількість похідних від хендла типів.

Win32

Тут же треба ще раз відмітити, що для Win32 API завжди застосовується 32х розрядна flat-модель пам'яті. У цьому випадку модифікатори far і near не застосовуються. Крім того хендл, відповідний типу unsigned int, стає 32х розрядним. Це насправді приводить до неабияких складностей при переході з платформи на платформу. Справа в тому, що в Windows API хендл часто об'єднується з якими-небудь додатковими даними і розміщується в одному двійчастому слові, що передається як параметр функції або повідомлення, а в Win32 таке вже не вийде - хендл сам займає все двійчасте слово.

Крім того, в Win32 API для роботи з файлами використовується знов-таки хендл, але вже не типу HFILE, а HANDLE. При цьому нульове значення як і раніше є допустимим і означає стандартний пристрій висновку, а значення -1 - невірний хендл. Для позначення невірного хендла файла визначений символ INVALID_HANDLE_VALUE, рівний -1. Для інших хендлов, крім хендлов файлів, цей символ не застосовується, оскільки для індикації помилки застосовується значення 0. При цьому тип HFILE і символ HFILE_ERROR визначені також, як і в 16ти розрядних Windows - у вигляді 16ти розрядного цілого числа. У принципі допустиме просте приведення типів, однак в майбутніх реалізаціях WindowsAPI ситуація може змінитися, оскільки тип HANDLE відповідає 32х розрядному числу.

Угорська нотація

При читанні текстів З-програм і документації Ви звернете увагу на трохи дивне написання імен змінних і функцій. Наприклад:

lpszFileName, wNameLength

Розробники Windows рекомендують застосовувати специфічні правила опису імен змінних, які отримали назву "угорська нотація" за національністю програміста Charles Simonyi з Microsoft, що запропонував її. Застосування угорської нотації поліпшує читаемость програм і зменшує імовірність помилки. Хоч, звісно, це дається ціною збільшення довжини імен змінних.

Хорошим программистским правилом є використання мнемонічних імен змінних. Угорська нотація передбачає не тільки застосування мнемоніки для визначення значення змінної (як, наприклад, FileSize), але і включення її типу в ім'я. Наприклад lpszFileName означає дальній покажчик на ASCIIZ [7] рядок символів, вмісний ім'я файла.

Як видно з прикладу, перед мнемонічним ім'ям змінної пишеться невеликий префікс, вказуючий її тип. Яким чином будується префікс? З невеликої таблиці можна отримати уявлення про позначення основних типів даних:

вказуючий символ

назва типу,

що означається пояснення

з

char

символ

by

BYTE

байт

n

int

ціле число

х

short

координата або розмір

у

short

координата або розмір

i

int

ціле число

f, b

BOOL

логічна величина

w

WORD

слово без знака

h

HANDLE

хендл

l

LONG

довге ціле зі знаком

dw

DWORD

довге ціле без знака

е

FLOAT

число з плаваючою комою

*fn

функція

s

рядок

sz

рядок, що закінчується ' \0' (ASCIIZ)

р

*

покажчик на. ..

lp

far*

дальній покажчик на. ..

np

near*

ближній покажчик на. ..

Знаючи цю таблицю легко самим зрозуміти або скласти імена змінних в угорській нотації. Навіть якщо Ви не будете самі застосовувати угорську нотацію при написанні програм, то знати її все одно треба, оскільки вона прийнята у всій документації, супроводжуючому Windows. На жаль навіть тут розробники виявилися не зовсім послідовні і Вам доведеться зіткнутися в документації з некоректним (з точки зору приведеної таблиці) застосуванням угорської нотації.

Так, як приклад можна привести назву поля cbWndExtra в структурі WNDCLASS. У цьому випадку префікс cb розшифровується як Count of Bytes.

Структура додатку Windows

Отже, ще раз подивимося на додаток 1a.cpp і пригадаємо, що треба зробити для написання додатку:

- написати віконну функцію;

- зареєструвати цю функцію в Windows;

- створити вікно, належне даному класу;

- забезпечити роботу додатку, обробляючи поступаючі вікну повідомлення.

У прикладі, що розглядається нами виконуються всі ці дії. Початковий код містить наступні функції: WinProc, init_instance і WinMain. Тут WinProc є віконною процедурою, init_instance реєструє клас вікон (віконну процедуру), а WinMain створює вікно і забезпечує роботу всього додатку.

Можна коротко розглянути основну послідовність дій, що відбуваються в системі при запуску додатку. Поки, що б не вникати в складності, обмежимося оглядом роботи 16ти розрядного додатку.

1. Операційна система завантажує файл, що виконується в пам'ять і виділяє необхідні для первинного завантаження ресурси.

2. Управління передається спеціально написаному розробниками компіляторів startup-коду, який ініціалізував додаток, отримує необхідну інформацію (як, наприклад, командний рядок, хендл копії додатку і пр.), запускає конструктори статичних об'єктів.

3. startup-код викликає функцію WinMain, передаючи їй отримані від операційна системи дані. Функція WinMain розробляється для кожного додатку.

4. WinMain звичайно здійснює реєстрацію класу вікон.

5. Далі WinMain створює і відображає головне вікно додатку.

6. WinMain забезпечує функціонування додатку, организуя цикл обробки повідомлень. У цьому циклі додаток витягує поступаючі до нього повідомлення, виконує їх попередню обробку і потім передає їх вікну-одержувачу (тобто викликає необхідну віконну процедуру).

7. Віконна процедура виконує обробку повідомлень, направлених вікну, забезпечуючи реакцію задачі на дії користувача.

8. Додаток працює, поки користувач не закриє головне вікно цього додатку. У момент закриття цього вікна віконна процедура вживає спеціальних заходів для завершення циклу обробки повідомлень, організованого WinMain.

9. Коли цикл обробки повідомлень завершується, WinMain продовжує виконання свого коду, виконуючи, при необхідності, деинициализацию і знищення створених об'єктів, після чого завершує роботу.

10. Повернення управління з функції WinMain відбувається знову-же в проміжний exit-код, створений розробниками компіляторів. Він запускає деструктори статичних об'єктів, деинициализирует додаток і повертає управління в систему.

11. Система звільняє ті, що залишилися зайнятими ресурси, закріплені за цим додатком.

Тепер, перед тим як перейти до розгляду безпосередньо коду додатку, треба зробити останнє зауваження. Windows API розроблявся тоді, коли систем віртуальної пам'яті на персональному комп'ютері ще не існувало. Перші версії Windows могли працювати на XT з 640K оперативної пам'яті. Через дуже обмежений об'єм пам'яті доводилося йти на різне хитрування. Так один з способів економії пам'яті пов'язаний з тим, що код додатків звичайно не змінюється. Якщо запустити дві копії одного і того-же додатки (скажемо, два Notepad'а для редагування двох різних файлів), то код цих додатків буде однаковим. У цьому разі його можна завантажувати тільки один раз, але для необхідності якось розрізнювати різні копії додатку і виникло поняття хендл копії додатку (instance handle, HINSTANCE).

Функція WinMain

Звичайна програма на З (З++) містить так звану головну процедуру main. При створенні програм для Windows також необхідно описати таку процедуру, правда вона називається WinMain і має інші аргументи:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)

{

//. ..

}

Опис головної функції ключовим словом PASCAL вказує на застосування угод мови Pascal при передачі аргументів і виклику функції (так робиться в більшості функцій Windows, тому що виклик pascal-декларированной функції здійснюється трохи швидше і займає менше місця, ніж С-декларірованной).

Розглянемо її аргументи:

HANDLE hInstance - цей параметр є хендлом, вказуючим конкретну копію додатку. Знання цього хендла зажадається для зв'язку тих або інакших даних з конкретною копією.

HANDLE hPrevInstance - описує хендл попередньої копії додатку. Якщо дана копія є першою, то ця змінна містить NULL. Використання цієї інформації декілька специфічно:

По-перше, Windows зв'язує деякі дані з конкретною копією додатку (наприклад: експортовані функції, вікна і пр.). При скріпленні необхідно вказувати хендл копії додатку.

Увага: при використанні З++ іноді зручно описати статичний об'єкт. Однак в цьому випадку може бути потрібна інформація об hInstance для конструктора статичного об'єкта. По дивній причині ми її не можемо взнати до виклику WinMain - ця інформація відома з самого початку (ще до виклику яких-небудь конструкторів): startup-код в самих перших інструкціях звертається до процедури INITTASK, яка повертає системну інформацію, в тому числі hInstance. Після цього hInstance копіюється в статичну змінну, що використовується startup- і exit- кодом, однак ця змінна є локальною (?!) і недоступна для інших модулів додатку. Причина такого вчинку з боку розробників компіляторів залишається незрозумілою.

По-друге, деякі операції повинні виконуватися тільки при запуску першої копії додатку, а всі інші копії повинні ігнорувати ці операції або виконувати замість них інші. Для пояснення цього звернемо увагу на віконну функцію. Коли ми створюємо додаток, то ми описуємо специфічні віконні функції і реєструємо класи вікон. Після запуску першої копії (поки вона активна) ці класи вікон відомі Windows. Значить при запуску подальших копій нам не треба їх реєструвати.

По-третє, іноді нам треба отримати дані від попередньої копії додатку (наприклад, якщо наші додатки організують обмін даними між собою). З допомогою hPrevInstance ми можемо зробити це (тільки в Windows API, в Win32 API це не вийде) [8].

В-четвертих, 32х бітові додатки Win32 API завжди передбачають, що запущена тільки одна копія додатку (оскільки у віртуальному адресному просторі додатку крім неї інших додатків, в тому числі копій, не знаходиться). При цьому hInstance вказує початкову адресу завантаження модуля (і для більшості додатків він співпадає), а hPrevInstance завжди рівний NULL.

LPSTR lpszCmdLine - як і звичайна З-програма, додаток Windows може отримувати командний рядок. Параметр lpszCmdLine є покажчиком на цей рядок.

int nCmdShow - цей параметр вказує, в якому вигляді повинне бути зображене вікно додатку. Для опису значень цієї змінної існує цілий набір #define'ов, що починаються з префікса SW_. Наприклад, значення nCmdShow рівне SW_SHOWMINNOACTIVE вказує на те, що вікно повинне бути відображене в мінімізованому стані, а значення SW_SHOWNORMAL вказує на необхідність відображення вікна в нормальному стані. Користувач може указати, в якому вигляді показувати головне вікно додатку, настроюючи характеристики ярлика (shortcut).

Реєстрація класу вікон

Після аргументів функції WinMain ми почнемо розглядати безпосередньо тіло цієї процедури. На самому початку ми повинні зареєструвати клас нашого вікна, пересвідчившись в тому, що це перша копія додатку. Для цього ми повинні заповнити структуру WNDCLASS і передати її функції RegisterClass:

WNDCLASS WC;

if (!hPrevInstance) {

WC.style= NULL;

WC.lpfnWndProc= WinProc;

WC.cbClsExtra= NULL;

WC.cbWndExtra= NULL;

WC.hInstance= hInstance;

WC.hIcon= LoadIcon(NULL, IDI_APPLICATION);

WC.hCursor= LoadCursor(NULL, IDC_ARROW);

WC.hbrBackground= GetStockObject(WHITE_BRUSH);

WC.lpszMenuName= NULL;

"WC.lpszClassName= Hello application";

if (!RegisterClass(&WC)) return NULL;

}

Ця операція звичайно виконується в окремій процедурі (init_instance в 1a.cpp) оскільки структура WNDCLASS використовується однократно, тільки для виклику функції RegisterClass, після чого її можна не тримати в стеку.

Структура WNDCLASS містить наступні поля:

Поле style містить комбінацію CS_xxx констант, задаючих деякі характеристики класу. Часто цей параметр рівний NULL. Для більш докладного знайомства з цим полем рекомендується подивитися опис функції RegisterClass і можливі стилі класу.

Наприклад, стиль CS_HREDRAW говорить про те, що вікно повинно перемальовуватися цілком при зміні його горизонтального розміру, CS_VREDRAW - при зміні вертикального розміру, CS_DBLCLK - вікно буде реагувати на двійчасті натиснення на клавішу миші. Особливо треба відмітити стиль CS_GLOBALCLASS. Звичайно зареєстрований клас використовується тільки лише даним додатком (і всіма його копіями), але недоступний для інших додатків. Стиль CS_GLOBALCLASS дозволяє використання класу іншими додатками.

Якщо Ви захочете об'єднати декілька стилів класу, використайте оператор ПОБІТОВЕ АБО, наприклад: CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLK.

lpfnWndProc є покажчиком на віконну функцію. Ви не можете указати його 0, ця функція повинна бути написана Вами.

cbClsExtra, cbWndExtra. При реєстрації класу вікна Windows створює спеціальний блок, вмісний інформацію про клас; У деяких випадках буває зручно включити в цю структуру свої дані (їх зможуть використати всі вікна, належні цьому класу - навіть вікна різних додатків).

Для цього Windows може зарезервувати спеціальний додатковий простір в цьому блоці, розмір цього простору вказується параметром cbClsExtra. Цей простір може бути використаний вами по своєму розсуду, тому ви повинні задати його розмір. Якщо Ви не маєте намір використати цей простір, укажіть його розмір 0(NULL).

Пізніше, при створенні вікна, Windows створить інший блок, що описує вікно, і в ньому виділить додатковий простір розміром cbWndExtra байт. Воно також призначене для використання Вами. Якщо воно Вам не потрібно, укажіть розмір 0 (NULL). У таких полях зручно зберігати дані, унікальні для кожного вікна - скажемо, хендл файла, що редагується, якщо це вікно редактора.

Малюнок 2. Структури даних, що використовуються Windows для опису вікон.

Ви можете використати при бажанні дванадцять функцій для читання/запису даних, що знаходяться в структурах опису вікна і класу:

UINTGetWindowWord(hWnd, nIndex);

UINT SetWindowWord(hWnd, nIndex, wNewValue);

LONG GetWindowLong(hWnd, nIndex);

LONG SetWindowLong(hWnd, nIndex, dwNewValue);

int GetWindowText(hWnd, lpWinName, nMaxCount);

int GetWindowTextLength(hWnd);

void SetWindowText(hWnd, lpszWinName);

"Текст вікна" (.. WindowText...). є звичайно заголовком вікна, а для вікон спеціального вигляду (наприклад, кнопки) - текстом, написаному в цьому вікні.

UINT GetClassWord(hWnd, nIndex);

UINT SetClassWord(hWnd, nIndex, wNewValue);

LONG GetClassLong(hWnd, nIndex);

LONG SetClasslong(hWnd, nIndex, dwNewValue);

int GetClassName(hWnd, lpClassName, nMaxCount);

Поле hInstance, звісно, містить хендл копії додатку, реєструючого даний клас вікон. Він буде використовуватися Windows для зв'язку віконної функції з копією додатку, вмісного віконну функцію.

Наступні три поля (hIcon, hCursor, hbrBackground) пояснити буде поскладніше - для цього треба буде розібратися з GDI (hbrBackground) і ресурсами (hIcon, hCursor), що буде зроблено у відповідних розділах. Тому поки що ми зробимо декілька зауважень і розглянемо найпростіший випадок.

hIcon - це хендл піктограми (іконки), яка буде виводитися замість вікна в мінімізованому стані. Функція LoadIcon знаходить відповідну піктограму і повертає її хендл. У нашому прикладі LoadIcon використовує стандартну піктограму (перший параметр рівний NULL), яка використовується за умовчанням для представлення додатку (другий параметр рівний IDI_APPLICATION).

hCursor є, аналогічне hIcon, хендлом курсора. Це курсор миші, який буде відображатися при переміщенні миші через вікно. Необхідний курсор завантажується функцією LoadCursor, застосування якої схоже на функцію LoadIcon. Для стандартних курсорів існують наступні імена: IDC_ARROW, IDC_CROSS, IDC_IBEAM, IDC_ICON, IDC_SIZE, IDC_SIZENESW, IDC_SIZES, IDC_SIZENWSE, IDC_SIZEWE, IDC_UPARROW і IDC_WAIT. Найчастіше уживаний курсор IDC_ARROW.

Третій хендл - хендл кисті - hbrBackground. Застосування кисті істотно відрізняється від піктограми і курсора, тому вона задається інакшим способом. Функція GetStockObject повертає хендл зазделегідь створеного Windows стандартного об'єкта. Ми використовуємо GetStockObject для отримання хендла "білої кисті" WHITE_BRUSH. Крім хендлов кистей ця функція може повертати хендли інших об'єктів. У прикладі 1a.cpp функція GetStockObject не застосовувалася - замість хендла кисті дозволено вказувати спеціальну константу, вказуючу системний колір (в прикладі - COLOR_WINDOW). Для того, що б система могла відрізнити хендл від кольору, потрібно, що б до нього була додана 1 (0 означає невірний хендл, будучи в той же час коректним номером кольору).

Потрібно відмітити, що кисть WHITE_BRUSH зовсім не обов'язково має білий колір. Просто ця кисть звичайно використовується для закраски фону вікна, часто це дійсно білий колір, але він може бути змінений при настройці кольорів Windows.

Покажчик на рядок lpszMenuName. Якщо Ваше вікно має пов'язане з ним меню, то Ви можете указати ім'я цього меню для його автоматичного використання. Як це робиться ми розглянемо дещо пізніше. У даному прикладі передбачається, що меню немає - lpszMenuName рівний NULL.

Останній параметр lpszClassName є ім'ям класу вікна, що реєструється. Рекомендується включати в це ім'я назву задачі, оскільки це зменшує імовірність збігу імені даного класу з вже зареєстрованим класом іншого додатку.

УВАГА! Якщо Ви застосовуєте російські символи в іменах меню, класу і інш., то Вам треба бути дуже обережними - стандартні шрифти нелокалізованих для Росії версій Windows 3.x не містять російських символів, і, крім цього, компілятори можуть не сприймати російський текст, особливо деякі його букви (як, наприклад, букву "я", код якої рівний 255). Додатково можуть зустрітися ускладнення через те, що середа програмування в Windows може використати власні нерусифіковані шрифти, або через те, що DOS-програми використовують інакше кодування російських символів, ніж Windows-додатки (якщо Ви використовуєте які-небудь DOS-кошти для розробки програм, або Ваш додаток буде використовувати DOS-програми).

Якщо функція RegisterClass повернула не 0, то клас успішно зареєстрований. Цей зареєстрований клас буде існувати доти, поки додаток активний, або поки Ви не викличете функцію

UnregisterClass(lpszClassName, hInstance)

для знищення даного класу. Якщо ви маєте намір викликати функцію UnregisterClass, то треба пересвідчитися що немає жодного вікна, належного даному класу. Ця функція звичайно застосовується для глобальних класів (зі стилем CS_GLOBALCLASS).

Створення і відображення вікна

Після реєстрації класу ми можемо створювати вікна. Робиться це за допомогою процедури CreateWindow. Ця функція створює вікно і повертає його хендл. Якщо створення вікна чомусь неможливо, то повертається значення NULL. Розглянемо параметри цієї функції:

HWND CreateWindow(szClassName, lpszWindowName, dwStyle,

nX, nY, nWidth, nHeight,

hWndParent, hMenu, hInstance, lpParam

);

lpszClassName задає ім'я класу вікна. На це ім'я Windows знаходить опис класу вікон і, відповідно, віконну функцію, яка буде викликатися для обробки повідомлень, що посилаються вікну. Такий механізм скріплення вікна з функцією (через ім'я класу) дозволяє визначати віконну функцію в одному додатку, а використати в іншому.

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

Параметр dwStyle містить бітові прапори, задаючі деякі характеристики вікна, якось тип рамки, наявність заголовка, кнопок системного меню, мінімізації, максимізації і т. д. Ознаки вікна називаються стилями і описані в windows.h з префіксом WS_. Ви повинні об'єднувати окремі стилі за допомогою операції ПОБІТОВЕ АБО: WS_CAPTION¦WS_SYSMENU.

Можливих стилів вікна дуже багато, ми їх будемо розглядати їх по мірі потреби. Для довідок треба звернутися до опису функції CreateWindow. Деякі комбінації ознак, що описують часто вживаний стиль вікна, зазделегідь визначені як окремі константи. Так, наприклад, стиль WS_OVERLAPPEDWINDOW описує "звичайне" вікно, що перекривається, що має кнопку системного меню, двох кнопки максимізації і мінімізації, заголовок вікна і "широку" рамку - для можливості змінювати розмір вікна.

Малюнок 3. Основні елементи вікна

nX, nY ці параметри задають позицію верхнього лівого кута вікна відносно екрана або, для дочірнього вікна, відносно верхнього лівого кута внутрішньої області батьківського вікна. Позиція задається в пікселях. Замість конкретних чисел можна підставити CW_USEDEFAULT - в цьому випадку Windows буде визначати позицію сам.

nWidth, nHeight - ширина і висота вікна в пікселях. Замість цих величин Ви можете указати CW_USEDEFAULT, для того, що б Windows самостійно визначив їх.

hWndParent цей параметр є хендлом батьківського вікна. Якщо Ви указали NULL, то дане вікно не є дочірнім (що використовується) [9], а якщо Ви задали хендл іншого вікна, то вікно, що знову створюється буде дочірнім (що використовується) по відношенню до вказаного.

hMenu звичайно задає хендл меню, що використовується вікном. Однак тут є декілька тонкості:

- якщо меню описане при реєстрації класу, і цей параметр рівний NULL, то буде використовуватися меню, описане в класі.

- якщо і при реєстрації класу меню не задане, і даний параметр рівний NULL, то вікно буде створене без меню, але Ви все одно зможете підключити меню до даного вікна пізніше.

- якщо дане вікно є дочірнім, то цей параметр задає не хендл меню, а індекс дочірнього вікна. Ви повинні призначити всім дочірнім вікнам одного батьківського різні індекси.

hInstance, як і раніше, є хендлом копії додатку, що використовує дане вікно. Зверніть увагу на те, що такий же хендл при реєстрації класу вказує на копію додатки, вмісну віконну функцію, а тут хендл вказує на копію додатки, що використовує дане вікно (і, в Windows API, може відрізнятися).

lpParam. Як правило, цей параметр рівний NULL. Він використовується порівняно рідко, звичайно якщо вікно є вікном MDI-інтерфейса (MultipleDocumentInterface) - об MDI дивися в розділі, присвяченому віконним системам. Цей покажчик передається віконній функції при обробці повідомлення, що інформує про створення вікна, і використовується безпосередньо самої віконною функцією. Так Ви можете передати віконній функції необхідні для створення вікна дані через цей параметр.

У деяких випадках при описі стилю вікна необхідно задавати додаткові характеристики, які не входять до складу параметра dwStyle. Так, наприклад, Ви можете створити вікно, яке завжди знаходиться понад всіх інших вікон (alwaysontop, topmost). Для цього використовується додаткове двійчасте слово стилю (extendedstyle) і, відповідно, інша функція для створення вікна:

HWND CreateWindowEx(ExStyle, lpszClassName, lpszWindowName, dwStyle,

nX, nY, nWidth, nHeight,

hWndParent, hMenu, hInstance, lpParam

);

Ця функція відрізняється тільки наявністю ще одного параметра dwExStyle, задаючого додаткові стилі вікна. Для завдання цих стилів використовуються мнемонічні імена з префіксом WS_EX_..., наприклад WS_EX_TOPMOST або WS_EX_TRANSPARENT.

Звичайно після створення вікна воно залишається невидимим, однак ми можемо указати на необхідність відображення вікна під час роботи функції CreateWindow за допомогою стилю вікна WS_VISIBLE.

Однак в WinMain не рекомендується створювати відразу видиме вікно. Це пов'язано з тим, що додаток може бути запущений в початково мінімізованому стані. Як треба відобразити вікно ми можемо взнати через аргумент nCmdShow функції WinMain, але використати його функцією CreateWindow складно. Тому вікно створюється невидимим і потім відображається в необхідному нам вигляді:

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

Функція ShowWindow вказує вікну, в якому вигляді воно повинне бути відображене на екрані. У функції WinMain параметр nCmdShow береться з переданих аргументів - він визначає, яким чином користувач хоче запустити цей додаток. Під час виконання функції ShowWindow на екрані відображається рамка і заголовок вікна в потрібному місці і необхідного розміру.

Під час виконання функції UpdateWindow відображається внутрішня область вікна. Визначимося, що внутрішня область вікна іноді називається клиентной (client), а рамка, заголовок, або замінююча все піктограма називаються зовнішньою областю, обрамленням (frame) або неклиентной (non-client) областю вікна. Для відображення внутрішньої області вікна додаток отримує спеціальне повідомлення, обробник якого виконує необхідне малювання.

Цикл обробки повідомлень

Зараз нам доведеться оглядово розглянути механізм передачі повідомлень в Windows 3.x (реально він набагато складніше, особливо в Windows-95 або Windows NT). Для початку потрібно виділити деяке джерело повідомлень. Їм може бути який-небудь пристрій введення (наприклад, клавіатура, миша), спеціальні пристрої (типу таймера), або який-небудь працюючий додаток. Повідомлення, отримані від джерела повідомлень, нагромаджуються в черзі повідомлень (messagequeue). Далі повідомлення будуть витягуватися з черги і передаватися для обробки конкретній віконній процедурі.

Для видобування з черги використовується спеціальний цикл обробки повідомлень (message loop), що розробляється для кожного додатку. У цьому циклі повідомлення витягуються з черги, визначається, яке вікно (яка процедура обробки повідомлень) повинне це повідомлення отримати, при необхідності виконуються спеціальні дії, і тільки потім повідомлення передається конкретній віконній процедурі. І такий процес працює весь час, поки працює додаток.

Процес приміщення повідомлення в чергу і його видобування з неї ніяк жорстко не пов'язаний за часом. Більш того може трапитися так, що послане повідомлення взагалі не буде оброблене, наприклад, якщо вікно-одержувач буде знищено раніше, ніж встигне його обробити.

Малюнок 4. Обробка посланих повідомлень в Windows

Наприклад, коли ви натискаєте на клавішу, генерується апаратне переривання. Клавіатурний драйвер Windows обробляє це переривання і вміщує відповідне повідомлення в чергу повідомлень. При цьому вказується, яке вікно повинно отримати дане повідомлення.

Цей процес називається посилкою (post) повідомлень, оскільки посилка повідомлення нагадує посилку листа: той, що посилає повідомлення вказує адресата, відправляє повідомлення і більше про нього не турбується. Відправник не знає, коли точно його повідомлення отримає адресата. Такий спосіб обробки повідомлень часто називається асинхронним.

Видобування повідомлень з черги додатку і напрям їх відповідним вікнам здійснює цикл обробки повідомлень, звичайно вхідний в функцію WinMain. Цей процес виконується в декілька прийомів:

- повідомлення вибирається з черги за допомогою функції GetMessage або PeekMessage

- потім повідомлення транслюється за допомогою функції TranslateMessage [10] (одне повідомлення може породжувати послідовність інших або замінюватися, як, наприклад, відбувається для повідомлень клавіатури 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;

Віконна процедура

Поки ми розглянули тільки функцію WinMain, причому вона буде без істотних змін зберігатися і в подальших прикладах. Тепер ми повинні написати віконну функцію. Суворо говорячи, описати її краще за передWinMain - тоді не треба описувати її прототип.

І ще одне зауваження: після виклику функції RegisterClass, реєструючої дану віконну процедуру, ви не повинні викликати її прямо - це приведе до помилки. Викликати цю функцію може тільки Windows. Пізніше ми взнаємо, чому це так і як можна її викликати самим.

Віконна функція повинна бути декларирована таким чином (у випадку Win32 API ключове слово _export може бути пропущене, детальніше про опис віконних функцій див. в розділі, присвяченому диспетчеру пам'яті):

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

//. ..

}

Як аргументи ми отримуємо параметри переданого повідомлення. Звичайно віконні функції оформляються приблизно по такій схемі:

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

// опис внутрішніх змінних

switch (uMsg) {

case WM_...:

// обробка потрібного повідомлення

break;

// обробка інших повідомлень...

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0L;

}

Головним елементом є конструкція switch, яка дозволяє написати обробку кожного окремого повідомлення, отриманого вікном. У об'єктних бібліотеках ці функції бере на себе базовий інтерактивний об'єкт, що дозволяє зв'язати певні методи класу з повідомленнями, що отримуються.

Для спрощення написання віконної функції Windows пропонує спеціальну функцію

LRESULT DefWindowProc(hWnd, uMsg, wParam, lParam);

Ця функція реалізовує стандартну обробку повідомлень, що дозволяє описати вікно, що має задані нами стилі і властивості і чисту внутрішню область. Тому ви повинні визначати обробку тільки тих повідомлень, які ви хочете обробляти нестандартним образом, а всі інші передавати цій процедурі.

Повідомлення, які отримує вікно, інформують його про самих різні події, що відбуваються з цим вікном, з додатком загалом, з самою системою і так далі. Зараз ми розглянемо чотири повідомлення, які застосовуються в розглянутому прикладі, і їх обробку.

Повідомлення WM_CREATE

Самим першим ми розглянемо повідомлення WM_CREATE. Це повідомлення посилається вікну в той момент, коли воно створюється. Реальним створенням вікна відає функція CreateWindow, а не обробник повідомлення WM_CREATE. Ви в цей момент повинні ініціалізувати свої змінні, виконати необхідні настройки, створити необхідні об'єкти і інше. При створенні вікно ще невидимо - тому Ви можете міняти його розміри, встановлювати в потрібне положення, міняти кольори не побоюючись мигтіння на екрані. Часто тут створюються необхідні структури і виділяються необхідні вікном ресурси.

Стандартна обробка цього повідомлення необов'язкова - функція DefWindowProc просто повертає 0 у відповідь на це повідомлення.

Параметр wParam не використовується, а параметр lParam містить покажчик [11] на структуру CREATESTRUCT. У цій структурі передається основна інформація про вікно, що створюється.

typedef struct tagCREATESTRUCT {

void FAR* lpCreateParams; // покажчик на додаткові дані,

// переданий як параметр lpParam у виклику

// функції CreateWindow або CreateWindowEx

HINSTANCE hInstance; // хендл копії додатку, що створила вікно

HMENUhMenu; // хендл меню (або NULL, якщо немає)

HWND hwndParent; // хендл батьківського вікна (або NULL)

intcy, cx; // розміри вікна

inty, х; // положення вікна

LONGstyle; // стилі вікна

LPCSTRlpszName; // заголовок вікна

LPCSTRlpszClass; // ім'я класу, до якого належить вікно

DWORDdwExStyle; // розширені стилі вікна (див. CreateWindowEx)

} CREATESTRUCT;

Поля х, у, cx і cy в момент обробки повідомлення WM_CREATE можуть бути ще не визначені. При необхідності отримати інформацію про розмір або положення вікна треба користуватися функціями GetWindowRect або GetClientRect, які повертають коректний результат

значення, що Повертається обробником:

не 0 - виникла помилка, вікно треба знищити (далі, при знищенні, буде отримане повідомлення WM_DESTROY), функція CreateWindow або CreateWindowEx поверне NULL.

0 - вікно успішно створене; функція CreateWindow або CreateWindowEx поверне хендл вікна.

Повідомлення WM_DESTROY і WM_QUIT

Ще одне повідомлення, що цікавить нас - WM_DESTROY. Це повідомлення посилається вікну в момент його знищення. Це одне з останніх повідомлень, яке отримує вікно - можна вважати, що після цього воно вже не існує, причому в момент отримання цього повідомлення вікно вже невидиме. У цей момент ви можете виконати необхідні операції по звільненню виділених при створенні вікна ресурсів. Обидва параметри повідомлення WM_DESTROY не використовуються.

Як і WM_CREATE повідомлення WM_DESTROY є інформаційним - реальне знищення вікна здійснюється функцією DestroyWindow, а не обробником повідомлення WM_DESTROY. Незалежно від способу обробки цього повідомлення вікно буде знищене.

Якщо ваша віконна функція обслуговує головне вікно додатку, то в момент знищення вікна ви повинні вжити заходів для завершення роботи всього додатку загалом. Для цього ви повинні послати повідомлення WM_QUIT, при видобуванні якого з черги закінчиться цикл обробки повідомлень. Якщо цього повідомлення не послати, то цикл обробки повідомлень продовжить роботу далі після закриття всіх вікон, що є. На практиці це означає що додаток взагалі перестане отримувати повідомлення і "зависне" на функції GetMessage, яка буде чекати, поки не прийде нове повідомлення. У випадку Windows 3.x є єдиний спосіб видалити такий додаток - перезапуск всього Windows (в Windows-95 або Windows NT такий додаток можна зняти самому за допомогою менеджера задач). Повідомлення WM_QUIT посилається за допомогою функції:

void PostQuitMessage(nExitCode);

Параметр nExitCode буде переданий як wParam повідомлення WM_QUIT і пізніше повернений функцією WinMain як параметр завершення.

Обробників для повідомлення WM_QUIT писати не треба, оскільки:

По-перше, функція GetMessage, отримавши це повідомлення просто поверне FALSE, так що цикл обробки повідомлень буде закінчений без трансляції і диспетчеризації цього повідомлення.

По-друге, це повідомлення адресоване не вікну, а додатку. Тобто, навіть якщо скористатися функцією PeekMessage для видобування повідомлення WM_QUIT з черги (на відміну від GetMessage це вийде), воно не буде відправлене ніякому вікну, оскільки хендл вікна-одержувача рівний NULL.

Повідомлення WM_PAINT

Останнє повідомлення, що розглядається нами - WM_PAINT. Обидва параметри повідомлення WM_PAINT не використовуються. З цим повідомленням нам доведеться розбиратися детальніше. Раніше, коли ми обговорювали розділення екрана між різними задачами, говорилося про те, що в Windows неможливо повністю виртуализовать всю роботу з екраном - тобто вміст невидимої в даній момент частини вікна для Windows залишається невідомим.

Уявимо собі, що невидима частина вікна стала видимою (наприклад, внаслідок переміщення іншого вікна) - тоді виникає необхідність відновити її образ. Windows не знає, що там повинне бути відображено, отже відображенням може займатися тільки сам додаток.

Для цього вводиться спеціальне повідомлення WM_PAINT, яке посилається вікну тоді, коли частина вікна (або все вікно) потребує перемальовування. Часто перемальовування вікна займає значний час, а обробку повідомлень рекомендується виконувати якнайшвидше; тому Windows особливим образом обробляє WM_PAINT, що б затримки в обробці цього повідомлення не впливали такого чином на роботу системи.

Отримавши це повідомлення, процедура обробки повідомлень повинна взнати, яку частину вікна треба перемалювати, виконати перемальовування, і повідомити Windows, що дана частина вікна тепер коректна. Ви можете перемальовувати не тільки цю частину вікна, а все вікно відразу - Windows прослідить, що б реальні зміни відбувалися тільки в тій частині, яка потребує перемальовування.

Додаток повинен бути побудований таким чином, що б, отримавши повідомлення WM_PAINT, воно могло перемалювати необхідну частину вікна. Тобто додаток повинен знати, що, де і як повинне бути намальоване у вікні, оскільки в будь-який момент може бути потрібне багаторазове перемальовування якої-небудь частини вікна.

Основи малювання у вікні

Ще раз пригадаємо, яким чином з'являється повідомлення WM_PAINT: це відбувається коли все вікно, або тільки його частина стають видимими. Та частина вікна, яка потребує перемальовування, є невірною - її вміст не відповідає необхідному. Ця частина отримала назву невірний прямокутник (invalidrectangle).

Відповідно, після перемальовування вона перестає бути невірною - тобто стає вірним прямокутником (validrectangle).

Тепер варто з'ясувати при яких умовах виникають невірні прямокутники і як вони використовуються. Для цього подивимося на ті ситуації, коли невірний прямокутник може виникнути, а коли немає:

Невірний прямокутник виникає, якщо:

- прихована область вікна (наприклад, закрита іншим вікном) ставати видимою.

- область вікна "прокручується" (за допомогою функцій ScrollWindow або ScrollDC). У цьому випадку та частина вікна, в якій повинен з'явитися новий, невидимий раніше, текст, появляється невірним прямокутником.

- вікно змінює розмір. Звичайно це приводить до появи невірних прямокутників тільки при збільшенні розміру вікна і тільки на поверхні, що додатково з'явилася. Однак, якщо при реєстрації класу вікна Ви указали стиль CS_HREDRAW або CS_VREDRAW, то все вікно цілком буде розглядатися як невірне.

- Вами викликана одна з функцій:

-

void InvalidateRect(hWnd, lpRect, fErase);

void InvalidateRgn(hWnd, hRgn, fErase);

Параметри цих функцій: hWnd - хендл вікна, вмісного прямокутник (або регіон), потребуючий перемальовування. lpRect - покажчик на структуру типу RECT, що описує прямокутник, або hRgn - хендл потрібного регіону. fErase - логічна (TRUE, FALSE) величина, вказуюча, чи треба відновлювати фон невірної області, чи ні.

Невірний прямокутник не з'являється при переміщенні курсора миші або піктограм через область вікна - Windows самостійно зберігає і відновлює вікно під курсором (або піктограмою).

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

Ще про повідомлення WM_PAINT

Допустимо, що внаслідок якої-небудь події з'явився невірний прямокутник. Що з ним відбувається далі?

Windows, виявивши невірний прямокутник, належний якому-небудь вікну, ставить в чергу додатку повідомлення WM_PAINT, адресоване цьому вікну. Віконна процедура, отримавши це повідомлення, перемальовує необхідну частину вікна (або велику), після чого повідомляє Windows про те, що невірний прямокутник виправлений.

Якщо цього не повідомити Windows, то буде вважатися, що прямокутник залишився невірним, і знов будуть генеруватися повідомлення WM_PAINT цьому вікну. Тому дуже важливо не залишати після WM_PAINT невірних прямокутників.

Таким чином здійснюється підтримка вікна в необхідному стані. Однак перемальовування є повільним процесом (до того ж багато які додатки перемальовують велику частину вікна, чим треба), а невірні прямокутники можуть з'являтися в декількох примірниках, що іноді перекриваються, до того, як додаток почне обробляти WM_PAINT.

Тому в Windows повідомлення WM_PAINT трактується не як повідомлення, а швидше як стан: якщо в черзі додатку є повідомлення WM_PAINT, то вікно треба перемалювати. А якщо в цей час з'являються нові невірні прямокутники, то нових повідомлень WM_PAINT в чергу не попадає, а просто новий невірний прямокутник об'єднується зі старим. Звичайно внаслідок об'єднання виникає деяка невірна область складної форми [12].

Повідомлення WM_PAINT є низкоприоритетним. Чим відрізняється низкоприоритетное повідомлення від нормального? Тим, що, якщо в черзі додатку є тільки повідомлення з низьким пріоритетом, то Windows може передати управління іншому додатку, що має в черзі повідомлення нормального пріоритету (в Windows тільки два повідомлення є низкоприоритетними: WM_PAINT і WM_TIMER).

Ці дві особливості дозволяють Windows порівняно легко перенести тривале малювання вікна, хоч при цьому поверхня, що перемальовується часто наближається до розмірів всього екрана. Специфічна обробка повідомлення WM_PAINT є причиною того, що в Windows рекомендується зосереджувати всі операції по висновку у вікно в обробникові повідомлення WM_PAINT.

Якщо Вам треба самим здійснити який-небудь висновок у вікно (зрозуміло, що така необхідність виникає і при обробці інших повідомлень), то настійно рекомендується наступний метод:

- всі операції по висновку сосредотачиваются в обробникові повідомлення WM_PAINT.

- коли у Вас виникає необхідність здійснити висновок у вікно, Ви викликаєте функцію InvalidateRect, яка маркірує потрібну частину вікна як невірну, і, отже, в чергу додатку попадає повідомлення WM_PAINT.

- при цьому повідомлення тільки лише виявляється в черзі, але воно не попадає на обробку негайно, реальний висновок станеться дещо пізніше. Якщо Вам треба саме зараз здійснити висновок у вікно, то додайте виклик функції UpdateWindow. Ця функція негайно передасть віконній процедурі повідомлення WM_PAINT для його обробки.

Звісно, здійснення висновку при обробці інших повідомлень не є помилкою, хоч робити цього не стоїть, особливо якщо малювання скільки-небудь складне. На жаль, завжди слідувати цьому правилу майже неможливо, однак потрібні ваговиті причини для його порушення.

При обробці повідомлення WM_PAINT варто дотримуватися трохи правил, специфічних для цього повідомлення:

Перше: Ніколи не передавайте повідомлення WM_PAINT безпосередньо вікну. Для цього існує функція UpdateWindow, яка генерує це повідомлення, якщо невірний прямокутник існує.

Друге: Рекомендується починати обробку повідомлення WM_PAINT з функції:

HDC BeginPaint(hWnd, lpPaintstruct);

і закінчувати функцією

void EndPaint(hWnd, lpPaintstruct);

де lpPaintstruct - покажчик на структуру типу PAINTSTRUCT. Ці функції виконують декілька потрібних додаткових дій для обробки повідомлення WM_PAINT, в тому числі оголошують внутрішню область вікна коректної.

Нарешті: Якщо все ж Ви не використовуєте BeginPaint.. EndPaint,. те обов'язково оголошуйте перемальовану область вірної за допомогою функції ValidateRect або ValidateRgn.

void ValidateRect(hWnd, lpRect);

void ValidateRgn(hWnd, hRgn);

Параметри цих функцій: hWnd - хендл вікна, вмісного прямокутник (або регіон), потребуючий перемальовування. lpRect - покажчик на структуру типу RECT, що описує прямокутник, або hRgn - хендл потрібного регіону.

Контекст пристрою

Розглядаючи повідомлення WM_PAINT і невірні прямокутники ми обговорили основні правила здійснення висновку в Windows. Тепер оглядово познайомимося з правилами здійснення виведення графічної у вікно.

У першому ж написаному додатку 1a.cpp ми побачили, що функція виведення TextOut використала не хендл вікна, а так званий хендл контексту пристрою (HDC, devicecontext). Навіщо він знадобився? і чому б не використати замість нього хендл вікна?

Справа в тому, що всі кошти висновку в Windows відносяться не до менеджера вікон, а до графічного інтерфейса пристроїв (GDI). GDI являє собою бібліотеку функцій для виконання графічного виведення на різних пристроях, не тільки на дисплеї. При створенні GDI прагнули зробити роботу з пристроями незалежною від самих пристроїв. Так, одні і ті ж функції, що здійснюють виведення, малювання ліній і т. д. можуть застосовуватися для роботи з дисплеєм, принтером, плоттер і іншими пристроями.

Для цього довелося ввести додаткове поняття - контекст пристрою, що ідентифікується його хендлом. Всі функції висновку взаємодіють з цим контекстом. Часто навіть говорять, що функції GDI малюють на контексті, а не безпосередньо на пристрої. Контекст пристрою описує так звані атрибути контексту і безпосередній характеристики пристрою, на якому здійснюється висновок.

Атрибути контексту пристрою незалежні від самого пристрою. Вони характеризують те зображення, яке буде малюватися. У число атрибутів входять кисті, пера, колір тексту, колір фону і багато що інше. Так, призначивши контексту пристрою поточне перо ви визначаєте товщину, колір і стиль (суцільна або переривиста) тих ліній, які будуть відображатися подальшими викликами функцій, що малюють ці лінії. При необхідності намалювати лінію іншого стилю ви повинні поміняти поточне перо. Аналогічно визначаються кисть, що використовується для закраски фону фігур, шрифт, вживаний при виведенні, і багато інших атрибутів контексту пристрою.

Інформація про пристрій описує безпосередньо можливості самого графічного пристрою. Функції GDI взаємодіють з пристроєм опосередковано - через контекст і через драйвер цього пристрою. Завдяки наявності інформації про пристрій одна і та ж функція GDI здатна здійснювати виведення на будь-який пристрій, для якого існує необхідний драйвер. Так, ви можете вибирати перо або гроно будь-якого кольору, а GDI вживе заходів, що б необхідне зображення було отримане як на кольоровому дисплеї, так і на чорно-білому принтері або плоттер.

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

Звичайно треба передбачати наступні варіанти:

- якщо додаток здійснює висновок тільки у вікно, то треба враховувати можливість роботи:

- з різним дозволом - від 640x400, 640x480 і до 1024x768, що часто зустрічаються, 1280x1024. Потрібно підкреслити, що в деяких випадках можливі режими роботи монітора тільки з 400 рядками розгортки, а не з 480, як вважають звичайно. Було б дуже бажано, що б навіть в такому режимі всі діалоги і вікна уміщалися на екрані.

- з різним числом кольорів - від 8 і до більш ніж 16 мільйонів кольорів (16 777 216). Чисто монохроматичні дисплеї (чорний і білий) вже практично не зустрічаються, а ось дисплеї дешевих переносних комп'ютерів часто дають тільки 8-16 градацій сірого; причому различимость кольорів може бути невелика.

- з різними настройками системної палітри; включаючи констрастні і енергосберегающие режими (іноді застосовуються для переносних комп'ютерів).

- якщо додаток здібно виводити на принтер, то треба мати на увазі, що замість принтера може виявитися плоттер, який добре малює лінії, але абсолютно не може виводити растрових зображень, або АЦПУ, яке здатне тільки друкувати текст. Перед виведенням потрібно перевіряти можливості даного пристрою [13].

Робота з контекстом пристрою

Оскільки для малювання на якому-небудь пристрої необхідно отримати хендл контексту цього пристрою, то природно треба розглянути основні правила роботи з контекстом і засобу для його отримання. Тут будуть оглядово розглянуті основні правила роботи з контекстом, для отримання більш докладної інформації потрібно звернутися до розділу, присвяченого роботі з графікою.

Власне існує дві групи методів отримання контексту пристрою - створення і отримання контексту пристрою. Різниця пов'язана з тим, що створення і, пізніше, знищення контексту пристрою займає деякий час. Якщо ви маєте намір здійснювати виведення на принтер, то ці витрати часу нікчемно малі в порівнянні згодом всім друку. Однак, якщо ви маєте намір тільки здійснювати малювання у вікні (яке може оновлюватися дуже часто), то навіть порівняно швидка операція створення контексту, повторювана багато разів, займе значний час. Тому в Windows існує декілька зазделегідь створених контекстів, відповідних дисплею. При висновку у вікно контекст створювати не треба, треба скористатися однією з функцій, що повертають такий зазделегідь заготований контекст пристрою.

Більш того в Windows методи, що створюють контекст, призначені для роботи з пристроєм цілком, а методи, що повертають вже існуючий - з вікном. Різниця полягає в застосуванні системи координат, пов'язаної з контекстом. У першому випадку система координат пов'язана з верхнім лівим кутом пристрою, а у другому випадку - з верхнім лівим кутом внутрішньої області вікна [14].

Зараз ми зосередимося тільки на двох способах отримання контексту пристрою і на деяких загальних правилах застосування цього контексту. З першим способом ми вже познайомилися - він заснований на функціях BeginPaint і EndPaint, а другої на функціях GetDC і ReleaseDC:

HDC GetDC(hWnd);

void ReleaseDC(hWnd, hDC);

Обидва способи повертають зазделегідь заготований контекст пристрою, однак роблять це по різному. Функції BeginPaint і EndPaint призначені для обробки повідомлення WM_PAINT. У інших випадках користуватися цими функціями не рекомендується. Це пов'язано з тим, що:

- ці функції оголошують вікно коректним

- контекст пристрою, що повертається відповідає навіть не внутрішній області вікна, а тільки невірної області. Тобто система координат контексту буде пов'язана з верхнім лівим кутом вікна, а ось область малювання буде відповідати тільки невірній області. При спробі малювати на такому контексті все, що не попадає в невірну область, не буде малюватися. Це зроблене для деякого прискорення перемальовування вікна.

- функція BeginPaint додатково вживає заходів до закраске фону тим пензликом, який був задана при реєстрації класу вікна. Це дозволяє при розробці обробника повідомлення WM_PAINT не піклуватися про закраске фону вікна.

Друга пара функцій (GetDC, ReleaseDC) цих операцій, що розглядається не робить, але зате вона повертає контекст для всієї внутрішньої області вікна, а не тільки для невірної області. При необхідності використати саме ці функції в обробникові повідомлення WM_PAINT необхідно самостійно вжити заходів до закраске фону і до оголошення вікна коректним.

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

У процесі малювання ви будете постійно змінювати атрибути контексту - вибирати нові кисті, пера, змінювати кольори і режими малювання і так далі. Всі ці зміни діють тільки в той час, поки контекст існує. Як тільки контекст звільняється (або знищується, якщо він був створений), то всі зміни, зроблені в його атрибутах, пропадають. Контекст, який ви отримуєте, практично завжди [15] настроєний стандартним образом.

Системи координат Windows

При малюванні на контексті пристрою вам доведеться задавати координати тексту, що виводиться і фігур, що зображаються в певній системі координат. Система координат, пов'язана з контекстом пристрою - система координат GDI, визначає правила перетворення координат х і у всіма функціями GDI. Ви можете самі визначати різні масштабні коефіцієнти по обох осях, задавати положення початку відліку цієї системи координат, або використати одну з стандартних систем координат GDI. У розділі, присвяченому виведенню графічних на контексті пристрою, ми детальніше познайомимося з цими системами координат.

Зараз же треба виділити декілька основних систем координат, вживаних Windows, і уточнити області застосування цих систем координат.

Перша система координат, що розглядається - система координат менеджера вікон. У цій системі вісь х направлена по горизонталі направо, вісь у - по вертикалі вниз. Початок відліку (0,0) пов'язана або з верхнім лівим кутом дисплея, або з верхнім лівим кутом внутрішньої області батьківського вікна. Ціна однієї одиниці цієї системи координат рівна одній одиниці пристрою (пікселю). Для перерахунку координат з системи відліку, пов'язаної з внутрішньою областю вікна в систему відліку, пов'язану з екраном (і навпаки) використовуються функції ClientToScreen і ScreenToClient.

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

При описі панелей діалогів використовується своя власна система координат - система координат панелі діалогу. У цьому випадку початок відліку вміщується у верхній лівий кут внутрішньої області панелі діалогу [16], орієнтація осей координат зберігається колишньої, а як одиниці відліку застосовують по осі х - одну четверту від середньої ширини символа шрифту, а по осі у - одну восьму від висоти шрифту. Звичайно ці величини приблизно відповідають одному пікселю. Додаткову інформацію можна отримати, наприклад, з опису функції MapDialogUnits.

Складність в застосуванні цієї системи координат пов'язана з поняттям середньої ширини символа. Справа в тому, що переважна більшість шрифтів є пропорційними - тобто кожний символ шрифту має свою ширину. Для обчислення «середньої ширини» застосовують ширину символів алфавіта, зважену з обліком частотність зустрічі символів в общелитературном тексті. Як правило - англійському. Все це може привести до деяких помилок в завданні положення і розмірів при використанні інакшої мови, чому англійський, особливо якщо при цьому використовуються нестандартні шрифти. У Windows-95 легко спостерігати цей ефект, змінюючи з допомогою панелі управління (controlpanel) розміри шрифтів, що використовуються і спостерігаючи за відображенням стандартних діалогів.

Як побудувати додаток

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

У даному розділі будуть розглянуті тільки основні кроки, з яких складається побудова додатку для Windows і початкові файли, які можуть для цього зажадатися. Всі середи побудови додатків будуть так чи інакше реалізовувати ці кроки і розробнику треба буде орієнтуватися в тих початкових файлах, які будуть застосовуватися або створюватися в процесі генерації файла, що виконується.

Як будували Windows-додатки на самому початку

Заздалегідь почнемо з історії, часів ранніх версій Windows (до 3.x). У ті часи, коли Windows тільки починав розвиватися, компілятори не містили практично ніяких коштів підтримки процесу створення додатків для Windows. У цьому випадку практично вся робота звалювалася на програмістів, які повинні були розробляти додаткові текстові файли, необхідні для побудови додатків.

Основні складності були пов'язані з двома особливостями додатків для Windows:

- додаток підтримував динамічне завантаження бібліотек. У випадку DOS все необхідне для роботи додатку знаходилося в файлі образу задачі, а у випадку Windows велика кількість функцій, необхідних додатку, міститься в бібліотеках, що динамічно завантажуються (часто це компоненти операційної системи). У таких випадках говорять, що додаток імпортує (import) функції. У той же час додаток повинно надавати частина своїх функцій для того, що б їх викликала операційна система (як, скажемо, віконна процедура). У цих випадках говорять, що додаток експортує (export) функції.

- додаток містив додаткові дані, звані ресурсами додатку (не плутати з ресурсами комп'ютера або операційної системи). У вигляді ресурсів додатку могли виступати піктограми, курсори, меню, діалоги і пр., що використовувалися додатком. Ці ресурси включалися в спеціальному форматі в файл образу задачі.

Звичайна DOS-задача не могла робити таких речей. Тому в Windows був прийнятий новий формат файла, що виконується, а для нормальної побудови образу задачі довелося змінити стандартний компоновщик (linker) так, щоб він міг використати інформацію про і функції, що імпортуються, що експортуються і збирати файли, що виконуються в новому форматі. Відповідно, такий компоновщик потребував інформації про і функції, що імпортуються, що експортуються, а також про деякі додаткові параметри Windows-додатку. Щоб надати цю інформацію компоновщику треба було написати спеціальний текстовой файл - файл опису модуля (def-файл).

У файлі опису модуля перераховувалися імена функцій, що експортуються, імена що імпортуються і бібліотеки, вмісні функції, належні імпорту, задавався розмір стека, давався короткий опис додатку і пр. Це було не дуже зручно, оскільки при виклику якої-небудь нової функції з Windows API (а їх більше за 1000), необхідно було додавати її ім'я в файл опису модуля.

У тих випадках, коли додаток потребував власних ресурсів, необхідно було окремо описати ці ресурси в спеціальному текстовому файлі опису ресурсів (rc-файлі). Разом з компіляторами до цих постачається спеціальний компілятор ресурсів RC.EXE, який сприймає файл опису ресурсів, компілює його і створює файл ресурсів додатку (res-файл). Потім той-же компілятор ресурсів може об'єднати вже побудований файл, що виконується з файлом ресурсів додатку.

Таким чином побудова додатку складалася з наступних етапів:

- розробка початкових текстів -. c,. cpp,. h,. hpp,. rc і. def файли;

- компіляція початкового тексту програми - з. c і. cpp отримуємо. obj (іноді. lib);

- компіляція ресурсів додатку - з. rc отримуємо. res;

- компонування додатку - з. obj і. lib отримуємо. exe;

- вбудування ресурсів додатку в файл, що виконується - з. exe і. res отримуємо робочий. exe.

Звісно, якщо власних ресурсів додаток не мав, то побудова задачі дещо спрощувалася. Але і при цьому необхідність перелічувати всі і функції, що імпортуються, що експортуються в файлі опису додатку була надто незручною.

Наступний етап

Очевидно, що необхідність перелічувати купу імен функцій в файлі опису додатку нікого не приводила в захоплення. Тому на наступному етапі була включена підтримка Windows-додатків безпосередньо в компілятори [17]. Для цього було додане ключове слово _export (іноді _export), яке застосовується при описі функцій, що експортуються безпосередньо в тексті З-програми. Для таких функцій компілятор включає в об'єктний файл спеціальну інформацію, так що компоновщик може правильно зібрати файл, що виконується. Так, наприклад, було зроблено в першому прикладі для віконної процедури:

LRESULT WINAPI _export proc(...

Крім експортованих функцій, в файлі опису додатку було необхідно перелічувати всі імпортовані функції. А цей список був самим великим (Windows експортує більше за 1000 функцій, які можуть імпортуватися додатками). Для рішення цієї задачі був змінений формат бібліотек об'єктних файлів, так що з'явилася можливість описати імена функцій, експортованих іншими модулями. Так компоновщик задачі може визначити, які функції імпортуються і з яких модулів.

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

Бібліотеки зі списками функцій, які можна імпортувати з Windows (тобто експортованих компонентами Windows) входять до складу всіх компіляторів. Іноді може виникнути необхідність використання нестандартного компонента (або власної бібліотеки, що динамічно завантажується ), для яких відповідної бібліотеки з посиланнями немає. У такому випадку можна скористатися спеціальним додатком - implib.exe - яке входить до складу більшості компіляторів (якщо його немає в складі компілятора, то значить його можливості реалізовані в якому-небудь іншому інструменті, як, наприклад, wlib.exe в Watcom З/З++). Implib дозволяє по файлу бібліотеки (. dll), що динамічно завантажується, що є або файлу опису проекту модуля бібліотеки (. def), вмісному список експортованих функцій, побудувати бібліотечний файл (. lib), з посиланнями на функції бібліотеки.

Спочатку, прагнучи максимально зменшити час завантаження модулів в пам'ять, при експортуванні функцій ним привласнювалися унікальні номери (призначаються розробником, або просто по порядку). Конкретна функція однозначно визначається ім'ям експортуючої бібліотеки динамічного завантаження і ідентифікатором функції. Для завдання ідентифікаторів функцій, що експортуються використовується файл опису модуля. Однак використання номерів замість імен є не дуже зручним для людини, тому в Windows використовуються обидва методи - функції доступні як по їх ідентифікаторах, так і по їх іменах. Для Windows API загальноприйнятим методом вважається використання ідентифікаторів - Microsoft стежить за тим, що б всі документовані функції зберігали свої ідентифікатори. А в Win32 API передбачається використання імен; більш того Microsoft не гарантує, що ідентифікатори документованих функцій не будуть змінюватися.

Побудова додатку зберегла в собі всі кроки, однак створення файла опису додатку стало необов'язковим. При розробці прикладних застосувань для Win32 API з файлами опису модуля практично не доводиться мати справу, він використовується в дуже рідких випадках для побудови бібліотек, що динамічно завантажуються. У цей час і у випадку Windows API, і у випадку Win32 API цей файл створюється тільки якщо необхідно забезпечити нестандартне компонування модуля, що має, наприклад, сегменти даних, що розділяються.

Сучасні компілятори і системи підтримки проектів фактично залишилися в рамках розглянутого порядку побудови додатку. Невеликі зміни реалізовані в різних компіляторах незалежно один від одного. Так, іноді включення файла ресурсів додатку в модуль, що виконується виконується не компілятором ресурсів, а безпосереднє компоновщиком; в інших випадках спеціальні редактори ресурсів дозволяють обійтися без побудови файла опису ресурсів (. rc), а створити безпосередньо файл ресурсів додатку (. res). Особливо часто це робиться в системах візуального програмування.

Якщо ви використовуєте яку-небудь систему підтримки проектів (Watcom IDE, Microsoft Developer Studio, Borland IDE, Symantec IDE і пр.) - а швидше усього це саме так - то ви повинні тільки прослідити за тим, що б необхідні файли були включені в проект. Система сама відстежить, як і коли повинен використовуватися той або інакший початковий файл.

Звичайно в проект включаються наступні файли:

- початкові тексти на З і З++ - файли з розширеннями. c,. cpp,. c++;

- файл, вмісний ресурси додатку, як правило тільки один, або. rc або. res;

- файли бібліотек. lib, вмісні або модулі, що статично компонуються, або посилання на функції бібліотек, що динамічно завантажуються (часто відсутні). Стандартні бібліотеки звичайно приєднуються компоновщиком за умовчанням, тому їх перелічувати тут не треба;

- додаткові об'єктні файли. obj - окремо включаються дуже рідко;

- файл опису модуля. def, тільки один, часто тільки при бажанні описати нестандартні параметри компонування (див. нижче, в описі цього файла).

Файл опису модуля (. def)

Файл опису модуля (додатки) містить звичайний текст, який можна написати будь-яким текстовим редактором. Як приклад розглянемо один з файлів опису модуля, використаний для побудови додатку testapp.exe:

NAME TESTAPP

DESCRIPTION 'TESTAPP - test application'

EXETYPE WINDOWS

PROTMODE

STUB 'WINSTUB.EXE'

CODE LOADONCALL MOVEABLE

DATA MOVEABLE MULTIPLE

STACKSIZE 8192

HEAPSIZE 4096

В цьому файлі відсутні списки експортованих і імпортованих функцій, оскільки використовувався компілятор для Windows 3.1, що здійснює скріплення з імпортованими функціями за допомогою бібліотек. Зараз ми розглянемо інформацію, яка може розміщуватися в цьому файлі.

Більшість компіляторів можуть використати власні директиви, а також власні розширення для параметрів, що задаються в директивах, не описані в загальному керівництві (як, наприклад, директива PROTMODE в приведеному прикладі). Крім того список можливих директив в файлах опису модулів для Windows API і для Win32 API розрізнюється.

Windows API

Перший рядок звичайно задає ім'я модуля. Якщо ви будуєте додаток, то першої повинна стояти директива NAME, а якщо бібліотеку, що динамічно завантажується - LIBRARY. Вказівка обох директив вважається некоректною. Ім'я повинно відповідати імені файла, так для testapp.exe цей рядок буде таким: NAME TESTAPP, а для mydll.dll - LIBRARY MYDLL.

LIBRARYdllname

NAMEexename

Звичайно наступний рядок - опис даного модуля. Вона починається з директиви DESCRIPTION, за якою слідує довільний текст, взятий в апострофи:

DESCRIPTION 'description text for module'

Наступна директива, якщо і присутній, то завжди визначає, що даний модуль призначений для роботи в середовищі Windows (аналогічні файли опису модулів можуть застосовуватися для OS/2).

EXETYPEWINDOWS

Ще дві директиви призначені для завдання розмірів стека і купи. Завдання розміру стека менше за 5K приводить до того, що Windows сам збільшує його до 5K. Завдання розміру купи взагалі абстрактне - головне, що б не 0, оскільки Windows буде збільшувати купу при необхідності (аж до найбільшого розміру сегмента даних додатку - 64K). Розмір купи 0 говорить про те, що вона просто не використовується..

HEAPSIZE size

STACKSIZE size

Дуже цікава директива - STUB. Про неї треба розказати трохи детальніше. Раніше було відмічено, що для Windows-додатків був розроблений власний формат модуля, що виконується. Очевидно, що спроба запустити такий модуль на старій версії DOS, який не розрахований на таку можливість, приведе до грубої помилки, аж до зависання комп'ютера або псування даних. Щоб цього уникнути, зробили так - Windows-додаток складається з двох частин:

- Перша частина являє собою невеликий додаток MS-DOS, звану заглушкою (stub). Звичайно цей додаток просто пише на екрані фразу типу "ThisprogrammustberununderMicrosoftWindows.". Заголовок цієї заглушки трохи змінений, щоб Windows міг відрізнити DOS-додаток від Windows-додатку, але ця зміна знаходиться в невживаній MS частині заголовка.

STUB'stubexe.exe'

Тут stubexe.exe - ім'я додатку-заглушки (можливе повне ім'я, разом з шляхом)

- Друга частина - власне код і дані Windows-додатки з власним заголовком.

Розробляючи власну заглушку можна робити цікаві додатки, працюючі в DOS і в Windows. Скажемо, зібрати в один файл, що виконується DOS-версію додатку і Windows-версію.

Ще три директиви використовуються для опису параметрів сегментів коду і даних. Директива CODE задає характеристики сегментів коду, DATA - сегментів даних, а SEGMENTS дозволяє описати характеристики для конкретного сегмента (в квадратні дужки [] укладені необов'язкові параметри, знак ¦ розділяє можливі варіанти; запис [FIXED¦MOVEABLE] означає, що може бути вказано або FIXED, або MOVEABLE, або нічого). Більш детально про характеристики сегментів додатку див. в описі диспетчера пам'яті Windows 3.x.

CODE [FIXED¦MOVEABLE] [DISCARDABLE] [PRELOAD¦LOADONCALL]

DATA [NONE¦SINGLE¦MULTIPLE] [FIXED¦MOVEABLE]

SEGMENTS

segname [CLASS 'clsname'] [minalloc] [FIXED¦MOVEABLE] [DISCARDABLE] [SHARED¦NONSHARED] [PRELOAD¦LOADONCALL].

..

Можуть бути вказані наступні параметри: FIXED або MOVEABLE - сегмент фіксований в пам'яті або переміщуваний; якщо сегмент переміщуваний, то він може бути таким, що втрачається (DISCARDABLE). Параметри PRELOAD і LOADONCALL вказують, коли сегмент повинен бути завантажений в оперативну пам'ять - при завантаженні додатку (PRELOAD) або при безпосередньому зверненні до нього (LOADONCALL). Параметри NONE, SINGLE або MULTIPLE застосовуються тільки для сегментів даних. NONE або SINGLE застосовується для бібліотек, що динамічно завантажуються; NONE - бібліотека не має сегмента даних взагалі, SINGLE - сегмент даних присутній в пам'яті в єдиному примірнику (динамічні бібліотеки завантажуються тільки один раз, інших копій не існує, всі додатки посилаються на одну бібліотеку з єдиним сегментом даних). MULTIPLE - сегмент даних завантажується для кожної копії додатку, застосовується тільки для додатків.

Директива SEGMENTS описує характеристики конкретних сегментів; segname - ім'я сегмента, clsname - ім'я класу сегмента, minalloc - мінімальний розмір сегмента. SHARED або NONSHARED повідомляє, чи є сегмент що розділяється різними копіями додатку, або немає. Після однієї директиви SEGMENTS може розміщуватися опис декількох сегментів, по одному на кожному рядку.

Ще дві що часто використовуються директиви - EXPORTS і IMPORTS. Вони задають списки експортованих і імпортованих імен функцій, опис кожної функції знаходиться на своєму рядку. У обох випадках треба враховувати наступну особливість - в Windows розрізнюють зовнішні і внутрішні імена. Внутрішні імена - це імена, використані при розробці початкового тексту програми, ті які міститися в тексті З-програми (або в об'єктному файлі). Зовнішні імена - імена що використовуються при експорті/імпорті, доступні іншим модулям. Зовнішнє і внутрішнє ім'я можуть не співпадати, тому передбачена можливість задавати обидва імена.

EXPORTS

exportname [=internalname] [@id] [RESIDENTNAME] [NODATA] [argsize]

...

В розділі EXPORTS перераховуються імена функцій, що експортуються даним модулем.][ Параметр exportname задає зовнішнє ім'я функції, під яким вона буде доступна іншим модулям, параметр internalname використовується, якщо зовнішнє і внутрішнє імена розрізнюються, @id задає ідентифікатор функції, argsize - якщо вказаний, повідомляє скільки слів в стеку займає список аргументів функції.][ Параметри RESIDENTNAME і NODATA використовуються надто рідко;][ RESIDENTNAME говорить про те, що ім'я функції повинно розміщуватися в так званому резидентному списку імен (який знаходитися в пам'яті постійно після завантаження модуля), звичайно імена розміщуються в нерезидентному списку, який завантажується при необхідності.][ NODATA говорить про те, що функція використовує сегмент даних зухвалого модуля, а не що експортує (детальніше - при розмові про диспетчера пам'яті).][

IMPORTS

[internalname=] modulename.id

[internalname=] modulename.importname

Розділ IMPORTS задає відповідність внутрішніх імен функцій (internalname), що імпортуються функціям, експортованим іншими модулями.][ Можливо два методи скріплення імен - по ідентифікатору (перший приклад), modulename - ім'я експортуючого модуля, id - ідентифікатор;][ або по іменах (другий приклад), importname - зовнішнє ім'я функції, під яким вона була експортована іншим модулем.][

Win32 API

Файл опису модуля в Win32 API використовується для завдання нестандартних характеристик бібліотек, що динамічно завантажуються. ][ Повний опис файлів опису модуля для Win32 API треба дивитися в документації, супроводжуючій вживаний компілятор.][

Оскільки файл опису модуля використовується для створення DLL, то перша директива - LIBRARY.][ Часто застосовується також директива EXPORTS для завдання ідентифікаторів функцій, що експортуються (обидві - див. в описі файла опису модуля для Windows API).][

Для завдання нестандартних параметрів окремих сегментів використовується директива SECTIONS, замінююча колишню директиву SEGMENTS.][ Синтаксис директиви SECTIONS трохи інакший, хоч допускається заміна слова SECTIONS на SEGMENTS:][

SECTIONS

secname [CLASS 'classname'] [EXECUTE] [READ] [WRITE] [SHARED]

Після вказівки імені секції (сегмента) слідує необов'язкова вказівка імені] класу і атрибутів цієї секції - дозвіл на виконання (EXECUTE), на читання (READ), на запис (WRITE) і оголошення секції що розділяється (SHARED) між різними копіями модуля (завантаженими в різних адресних просторах різних додатків!).

Додаткові розділи

В цьому розділі буде розказано про маловідомий заголовний файл - windowsx.h. У деяких випадках розробники його, звісно, використовують, але рідко більше ніж на 5% від його можливостей. Цей заголовний файл був розроблений спеціально для полегшення контролю початкового тексту програми. На жаль, в більшій частині документації, супроводжуючій компілятори, цей файл взагалі не описаний, хоч існує вже дуже давно. Мабуть уперше він описаний в документації, супроводжуючій Microsoft Visual З++ 4.0 (Microsoft Developer Studio, розділ "SDKs¦Win32 SDK¦Guides¦Programming Techniques¦Handling Messages with portable macros"). Однак навіть там описані тільки принципи його застосування, але не даний докладний опис всіх його макросів. Як результат - при застосуванні windowsx.h доводиться постійно звертатися до його початкового тексту.

Заголовний файл WINDOWSX.H

В Windows часто буває так, що одна і та ж функція може працювати з об'єктами різних типів. У таких випадках буває важко прослідити коректність її застосування. Для цього в windowsx.h включено багато макросів, які перевизначити виклики таких функцій, що б текст вийшов таким, що більш читається. Саме цю можливість windowsx.h іноді застосовують на практиці.

Наприклад функція DeleteObject може застосовуватися для видалення багатьох об'єктів GDI (Graphics Devices Interface) - олівців, грон, регіонів і пр. По назві функції зрозуміти, який-саме об'єкт вона видаляє не можна, тому при читанні початкового коду доводиться сосредотачиваться на параметрах цієї функції. У windowsx.h визначені макроси:

#define DeletePen(hpen) DeleteObject(HGDIOBJ)(HPEN)(hpen)

#define DeleteBrush(hbr) DeleteObject(HGDIOBJ)(HBRUSH)(hbr)

#define DeleteRgn(hrgn) DeleteObject(HGDIOBJ)(HRGN)(hrgn)

при використанні яких текст стає що більш читається і легше знаходяться помилки.

При включенні файла windowsx.h у ваш додаток цей треба робити після включення основного файла windows.h:

#define STRICT

#include < windows.h >

#include < windowsx.h >

Для того, що б отримати уявлення про можливості windowsx.h рекомендується подивитися його початковий текст. У ньому присутні наступні розділи:

- макроси для роботи з функціями ядра (декілька макросів для роботи з глобальною пам'яттю)

- макроси для роботи з об'єктами GDI

- макроси для роботи з вікнами (виклики стандартних функцій)

- распаковщики повідомлень (сама велика частина)

- макроси для роботи з вікнами стандартних класів (кнопки, списки і пр.)

- деякі макроси для оптимізації стандартної бібліотеки часу виконання

В процесі розгляду відповідних розділів ми часто будемо звертатися до цього заголовного файла. Тут же ми детально познайомимося з використанням распаковщиков повідомлень.

Распаковщики повідомлень

Велика частина файла windowsx.h призначена для опису распаковщиков повідомлень (messagecrackers). Оскільки в книзі переважно будуть приводяться фрагменти коду з використанням распаковщиков, то в цьому місці ми з ними і познайомимося. При їх використанні доведеться постійно заглядати в початковий текст windowsx.h, оскільки в звичайній документації распаковщики не описані. По щастю цей файл добре структурований і забезпечений достатніми коментарями.

Для того, що б зрозуміти його призначення, повернемося до віконної процедури. Основна її частина - конструкція switch, яка потрібна для обробки конкретних повідомлень. Звичайно (крім найпростіших прикладів) цей switch розростається до неймовірних розмірів, так що його надто важко прочитати (і не дай боже шукати там помилку). Другим побічним ефектом є те, що безліч змінних, що використовуються для обробки різних повідомлень, сосредотачиваются в одній процедурі, марно поміщаючись.

Цілком очевидний вихід - рознести обробку повідомлень по окремих функціях, які будуть викликатися з процедури обробки повідомлень. Однак для кожного повідомлення передаються свої дані, упаковані в двох параметрах - wParam і lParam. Іноді вони не використовуються, іноді містять які-небудь значення, іноді - покажчики. Природно, було б зручним передавати в функцію, що викликається вже розпаковані параметри. Ускладнення тут викликає те, що для Windows API і для Win32 API одні і ті ж дані одного і того ж повідомлення можуть бути упаковані по різному [18].

При розробці windowsx.h це все було враховане (для Windows API і для Win32 API распаковщики визначаються по різному). Так, для кожного повідомлення WM_xxx визначений свій макрос з ім'ям HANDLE_WM_xxx. Наприклад, для повідомлення WM_CREATE визначений макрос:

HANDLE_WM_CREATE(hwnd, wParam, lParam, fn)

Параметри всіх макросів однакові, що дозволяє передавати їм безпосередньо параметри повідомлення (вікно-адресат hwnd, параметри wParam і lParam), а також ім'я функції-обробника fn. Цей макрос повинен використовуватися всередині конструкції switch для виклику потрібної функції і передачі їй розпакованих параметрів. Наприклад фрагмент наступного вигляду:

switch (uMsg) {

case WM_CREATE: return HANDLE_WM_CREATE(hWnd, wParam, lParam, fnOnCreate);

// ...

}

буде перетворений компілятором в наступний фрагмент (детальніше див. початковий текст windowsx.h):

switch (uMsg) {

case WM_CREATE:

return ((fnOnCreate)(hWnd)(, (LPCREATESTRUCT)(lParam))?

0L: (LRESULT)-1L);

// ...

}

Тобто при розкритті макроса HANDLE_WM_xxx здійснюється розпаковування параметрів, виклик функції і аналіз результату, що повертається. Тут, до речі, прихована одна пастка (по щастю надто рідка): результат, що повертається функцією-обробником не завжди буде співпадати з результатом, описаним в довіднику для даного повідомлення. Випадок з WM_CREATE саме такої - згідно з описом обробник WM_CREATE повинен повернути 0L, якщо гаразд. А, як ми бачимо в приведеному фрагменті, функція, що викликається распаковщиком, повинна повернути TRUE, тобто не 0, якщо гаразд (распаковщик сам замінить TRUE на 0L).

При розгляді цього прикладу виникає питання - а як повинна бути описана функція-обробник, що б распаковщик її правильно викликав? Відповідь проста - в самому файлі windowsx.h перед визначенням відповідного макроса приводиться прототип цієї функції. Тобто нам треба зробити наступне: відкрити windowsx.h, знайти в ньому рядок, де визначається распаковщик для WM_CREATE (це легко робиться пошуком) і подивитися на приведений там текст:

/* BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) */

#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \

((fn)((hwnd), (LPCREATESTRUCT)(lParam))? 0L: (LRESULT)-1L)

#define FORWARD_WM_CREATE(hwnd, lpCreateStruct, fn) \

(BOOL)(DWORD)(fn) (hwnd), WM_CREATE, 0L, \

(LPARAM)(LPCREATESTRUCT)(lpCreateStruct) функції Cls_OnCreate ми і шукаємо. Далі нам треба його просто скопіювати в наш додаток і виправити при бажанні ім'я функції. Єдине, що залишається не дуже зручним - так це виклик макроса-распаковщика - уже дуже довгий рядок виходить. Для цього в windowsx.h міститься окремий невеликий макрос:

HANDLE_MSG(hWnd, uMsg, fn)

Використовується він таким способом:

switch (uMsg) {

HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);

// ...

}

При цьому він сам вставляє "case WM_xxx: return..." і інше. Важливо стежити, що б в описі віконної процедури параметри wParam і lParam називалися саме так і не інакше. Справа в тому, що HANDLE_MSG при зверненні до макроса HANDLE_WM_xxx вказує йому саме ці імена.

Щоб закінчити розмову про распаковщиках повідомлень треба відповісти тільки на два питання - навіщо потрібні макроси FORWARD_WM_xxx, визначені в тому-же windowsx.h і як можна додати распаковщики для яких-небудь повідомлень, там не визначених (наприклад, нестандартних).

Розглянуті поки макроси-распаковщики дозволяли нам викликати функція-обробник повідомлення, маючи для неї дані, упаковані в wParam і lParam. Однак іноді виникає інша необхідність - маючи параметри функції передати яке-небудь повідомлення. Для цього призначені макроси FORWARD_WM_xxx. Для використання цих макросів, необхідно, щоб функція, одержуюча параметри повідомлення, мала наступний вигляд:

LRESULT proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Макроси FORWARD_WM_xxx отримують як параметри розпаковані дані (як і функція-обробник), упаковують їх в параметри повідомлення і викликають вказану функцію. По щастю практично всі функції, які доведеться викликати за допомогою макросів FORWARD_WM_xxx (SendMessage, PostMessage, DefWindowProc і пр.) відповідають приведеному опису.

Наприклад, повідомлення WM_SETFONT посилається вікну (стандартного класу) для того, що б призначити йому потрібний шрифт. Параметри цього повідомлення наступні: wParam містить хендл шрифту, а молодше слово lParam вказує, надоли перемальовувати вікно відразу після зміни шрифту. Передбачимо, що ваше вікно має дочірнє вікно, і вам хочеться зробити так, щоб при зміні шрифту у вашому вікні одночасно мінявся шрифт в дочірньому. Відповідно ви повинні включити у віконну процедуру обробку повідомлення WM_SETFONT і в його обробникові передати таке-же повідомлення дочірньому вікну.

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw);

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg) {

//. ..

HANDLE_MSG(hWnd, WM_SETFONT, Cls_OnSetFont);

// ...

}

}

// ...

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw)

{

HWND hwndChild =. ..; // определениехендладочернегоокна

FORWARD_WM_SETFONT(hwndChild, hfont, fRedraw, SendMessage);

}

Тут, до речі, можна було б скористатися макросом SetWindowFont з того ж windowsx.h. Цей макрос звертається до FORWARD_WM_SETFONT, як в розглянутому прикладі, однак текст при цьому стає таким, що більш читається:

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw)

{

HWND hwndChild =. ..; // визначення хендла дочірнього вікна

SetWindowFont(hwndChild, hfont, fRedraw);

}

Додавання власних распаковщиков не повинне викликати великих ускладнень - досить тільки розробити реалізації макросів HANDLE_WM_xxx і FORWARD_WM_xxx аналогічно вже зробленому в windowsx.h.

Приклад 1B - використання распаковщиков повідомлень

Цей приклад ілюструє застосування распаковщиков повідомлень на прикладі найпростішого додатку. Фактично він відповідає злегка зміненому прикладу 1A, в якому віконна процедура переписана для використання распаковщиков повідомлень. Функція WinMain в цьому прикладі залишилася без змін.

#"defineSTRICT

#include < windows.h >

#include < windowsx.h >

#define UNUSED_ARG(arg) (arg)=(arg)

static char szWndClass[] = test window";

BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)

{

UNUSED_ARG(hwnd);

UNUSED_ARG(lpCreateStruct);

return TRUE;

}

void Cls_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps;

BeginPaint(hwnd, &ps);

TextOut(ps.hdc, 0, 0, "Hello, world!, "13);

EndPaint(hwnd, &ps);

}

void Cls_OnDestroy(HWND hwnd)

{

UNUSED_ARG(hwnd);

PostQuitMessage(0);

}

LRESULT WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg) {

HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);

HANDLE_MSG(hWnd, WM_PAINT, Cls_OnPaint);

HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);

default: break;

}

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

static BOOL init_instance(HINSTANCE hInstance)

{

WNDCLASS wc;

wc.style = 0;

wc.lpfnWndProc = WinProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szWndClass;

return RegisterClass(&wc) == NULL? FALSE: TRUE;

}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

UNUSED_ARG(lpszCmdLine);

MSG msg;

HWND hWnd;

if (!hPrevInst) {

if (!init_instance(hInst)) return 1;

}

hWnd= CreateWindow( szWndClass, // class name

"window header", // window name

WS_OVERLAPPEDWINDOW, // window style

CW_USEDEFAULT, CW_USEDEFAULT, // window position

CW_USEDEFAULT, CW_USEDEFAULT, // window size

NULL, // parent window

NULL, // menu

hInst, // current instance

NULL // user-defined parameters

);

if (!hWnd) return 1;

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

while (GetMessage(&msg, NULL, NULL, NULL)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

Трохи про об'єкти

Тут ми розглянемо деякі основні особливості реалізації об'єктно-орієнтованого програмування в Windows. Останнім часом набули величезного поширення бібліотеки об'єктів для створення додатків в середовищі Windows. Особливо широко вони стали розповсюджуватися з розвитком систем візуального програмування. Найбільше поширення отримали бібліотеки об'єктів компаній

- Borland - Object Windows Library (OWL), підтримується компіляторами Borland З++ (розглядається версія v2.5, супроводжуюча компілятор Borland З/З++ v4.5).

- Microsoft - Microsoft Foundation Classes (MFC), підтримується найбільшою кількістю компіляторів, серед яких Microsoft Visual З++, Watcom З++, Symantec З++ і інші (розглядається версія v4.0, супроводжуюча Visual З/З++ v4.0).

Такі бібліотеки досить многофункциональни і громіздкі, розмір файла, що виконується, створеного з їх допомогою рідко буває менше 300-400K. Звісно, при розробці великих систем, підтримуючих такі інструменти як OLE, DAO або WOSE, реєструючу свої власні типи файлів і т. д., використання цих бібліотек може істотно скоротити час, необхідний для розробки прикладного застосування.

Ці бібліотеки об'єктів, хоч і мають безліч відмінностей, неминуче мають і багато загального, що визначається платформою, на якій вони працюють - Windows. Для забезпечення ефективної роботи додатків ці бібліотеки вимушені передбачати простий механізм доступу за допомогою методів об'єктів до функцій API, що їх неминуче зближує між собою. Крім того реалізація і ієрархія об'єктів в Windows неминуче приводить до появи чимсь схожої ієрархії класів в бібліотеках ООП.

У цьому розділі ми розглянемо найпростіший додаток в середовищі Windows, побудований коштами ООП, причому всі класи будуть оригінальними - ні MFC, ні OWL не застосовується. Це зроблене для того, що б "витягнути" на поверхню деякі аспекти розробки класів для Windows-додатків. Тут будуть використовуватися істотно спрощені методи реалізації об'єктів, в порівнянні з "великими" бібліотеками.

Можливо, що в деяких окремих випадках використання такого підходу може виявитися і більш продуктивним, ніж застосування MFC або OWL. Особливо, якщо ваш додаток схожий на найпростіше "Hello, world!" (в цьому випадку, правда, ще зручніше може бути обійтися зовсім без класів).

Особливості ООП в Windows

Насправді Windows не є справжньою об'єктно-орієнтованою середою. Хоч вікно і може бути названо об'єктом ООП, але лише з достатньою натяжкою. Саме істотна відмінність вікна в Windows від об'єкта ООП полягає в тому, що повідомлення, що обробляється віконною функцією, в багатьох випадках не виконує дій, а є "інформаційним", вказуючи на те, що над вікном виконується та або інакша операція якою-небудь зовнішньою функцією.

Пояснимо це на прикладі створення вікна. У випадку ООП для знищення об'єкта він повинен отримати повідомлення "destroy", обробка якого приведе до його знищення. У Windows повідомлення WM_DESTROY не виконує ніяких функцій по знищенню вікна. Воно тільки інформує вікно про те, що в цей час вікно знищується коштами звичайної функціональної бібліотеки, наприклад за допомогою функції DestroyWindow. Ви можете взагалі ігнорувати це повідомлення, повертати будь-яке значення, викликати або не викликати функцію обробки за умовчанням - вікно все одно буде знищене.

З точки зору реалізації об'єктів це приводить до того, що велика частина методів представлена в двох варіантах - один викликає відповідну функцію API, а інший викликається при обробці відповідного повідомлення. Так у разі функції DestroyWindow і повідомлення WM_DESTROY для класу, що представляє вікно, буде існувати два методи: метод Destroy і метод OnDestroy (назви методів умовні, в реальних бібліотеках вони можуть відрізнятися). Метод Destroy буде відповідати виклику функції DestroyWindow, а метод OnDestroy - обробнику повідомлення WM_DESTROY.

На цьому, на жаль, складності не кінчаються. Допустимо, що ви хочете зробити так, що б вікно не знищувалося. На перший погляд треба перевизначити метод Destroy, що б він не викликав функцію DestroyWindow; при цьому виклик методу Destroy не буде знищувати вікно. Однак все набагато складніше: вікно як і раніше може бути знищене прямим зверненням до функції API - DestroyWindow. Більш того стандартні обробники повідомлень (тобто належні Windows, а не бібліотеці класів) так і роблять. Так стандартна обробка повідомлення WM_CLOSE приводить до виклику функції DestroyWindow (а не методу Destroy). Тобто для рішення подібної задачі треба перевизначити всі методи об'єкта і всіх обробників повідомлень, які посилаються на відповідну функцію API. Задача що фактично не вирішується - особливо з обліком недостатньо докладній і точній документації.

Все це приводить до того, що для написання надійних додатків з використанням бібліотек класів доводиться дуже добре уявляти собі як працює Windows, яким функціям або повідомленням API відповідають ті або інакші методи, а, крім того, як це реалізоване в конкретній бібліотеці.

Базові класи додатку

Коли розробляється звичайний додаток на З або З++ без використання класів, то треба розробити як функцію WinMain, що визначає роботу додатку загалом, так і віконну процедуру, що визначає реакцію вікна на зовнішні впливи. При застосуванні об'єктів ці функції покладаються на методи класів. Природно, що і в MFC, і в OWL існують класи, призначені як для опису додатку загалом, так і для опису вікон. Ці класи повинні використовуватися як класи-предки, від яких породжуються класи, що описують ваш додаток і його головне вікно.

Класи, що описують додаток, в кінцевому результаті беруть на себе роботу, що виконується функцією WinMain, - вони реєструють спеціальний клас вікон в Windows, організують створення і відображення головного вікна додатку і цикл обробки повідомлень, організують ініціалізацію і створення необхідних об'єктів при запуску додатку і їх знищення при завершенні. Причому, оскільки зазделегідь не відомо, як будуть називатися класи, розроблені користувачем, і скільки і яких вікон треба буде створювати, то бібліотеки пропонують тільки лише базові класи, на основі яких треба розробити свої власні класи для опису додатку і вікна.

MFC

У бібліотеці Microsoft Foundation Classes для опису додатку використовуються наступні класи:

Малюнок 5. Класи MFC, що описують вікно і додаток.

Дана версія MFC розрахована на розробку многопотокових прикладних застосувань для Win32. Це наклало свій відбиток на ієрархію класів - як одне з базових виступає клас CWinThread, що описує окремий потік. І тільки на його основі побудований клас CWinApp, що описує додаток (в Win32 існує поняття основного потоку, який обслуговує функцію WinMain, саме він і буде представлений об'єктом класу CWinThread, на основі якого породжується об'єкт класу CWinApp).

OWL

У цій бібліотеці ієрархія класів дещо інакша - бібліотека розрахована на розробку 16ти розрядних прикладних застосувань Windows 3.x, не підтримуючу потоків.

Малюнок 6. Класи OWL, що описують вікно і додаток.

Вікна ООП і вікна Windows

При використанні бібліотек класів треба якось зв'язувати примірники об'єктів, що описують вікна в додатку, з описом того-же вікна в Windows. Тут треба враховувати, що, з одного боку:

- існують методи класів, відповідні виклику функцій API;

- існують методи класів, відповідні обробникам повідомлень;

а, з іншого боку:

- існують вікна, створені за допомогою класів ООП;

- існують вікна, створені іншими додатками, модулями а також стандартними коштами Windows, що не мають відношення до вживаної бібліотеки.

Так, наприклад, діалог "Відкрити Файл" є стандартним діалогом Windows. Він створюється і виконується за допомогою виклику однієї функції API - FileOpen. Ця функція сама, незалежно від додатку і його бібліотеки класів, створює необхідні вікна і працює з ними. Однак у програміста може виникнути необхідність якось взаємодіяти з цим діалогом в процесі його роботи.

Можна виділити чотири можливих ситуації, з якими доведеться зіткнутися під час роботи додатку:

1. повинна бути викликана функція API для вікна, реалізованого як об'єкт класу;

2. повинна бути викликана функція API для вікна, що не є об'єктом класу;

3. вікно, реалізоване як об'єкт класу, отримує повідомлення - тобто треба викликати відповідний метод-обробник цього повідомлення;

4. вікно, що не є об'єктом класу, отримує повідомлення.

Випадки 1 і 2 вирішуються порівняно просто - серед членів-даних класу повинен бути присутній член, задаючий хендл вікна в Windows. У такому випадку виклик функцій API, потребуючих в хендле, відбувайся елементарно. Невеликий нюанс пов'язаний з вікнами, що не є об'єктами класу. Наприклад, діалоги, включаючи стандартні і їх елементи управління - кнопки, прапорці і інше, часто створюються як вікна, належні Windows. Тобто спочатку, в момент їх створення, не існує об'єктів додатку, відповідних цим вікнам. Для цього в бібліотеку вводяться кошти створення об'єктів по хендлу. Ці кошти можуть дещо розрізнюватися в різних бібліотеках.

Наприклад метод CWnd* CWnd::FromHandle(HWND), існуючий в MFC, створює спеціальний об'єкт, що описує вікно, зв'язує його з вказаним хендлом і повертає покажчик на нього. Цей об'єкт вважається "тимчасовим" - через деякий час MFC сама його знищить. У OWL аналогічного ефекту можна добитися використовуючи спеціальну форму конструктора об'єкта TWindow.

Випадок 3 істотно більш складний. Коли вікно отримує повідомлення, Windows викликає зареєстровану віконну процедуру, причому для цієї процедури передається тільки хендл вікна і параметри повідомлення. Покажчик на об'єкт додатку, що описує це вікно, залишається в момент отримання повідомлення невідомим!

Звичайно в бібліотеках класів використовують наступний спосіб: при запуску додатку в Windows реєструється віконна процедура, що визначає спеціальний клас вікон, що використовується всією ієрархією класів даної бібліотеки. Ця віконна процедура отримує повідомлення і виконує дві операції:

- знаходить пов'язане з даним хендлом вікно - для цього бібліотеки класів підтримують спеціальні таблиці відповідності хендлов вікон описам цих вікон в додатку

- розподіляє повідомлення, що прийшло відповідному (звичайно віртуальному) методу-обробнику цього повідомлення.

Для завдання методів-обробників конкретних повідомлень вводяться спеціальні таблиці відгуку або таблиці трансляції повідомлень (responsetable, messagemaptable). Коли ви розробляєте новий клас вікон, ви для нього повинні розробити таку таблицю, в якій повинні бути вказані відповідності приходячих повідомлень і методів, що викликаються (звісно, якщо це не зроблене в класі-предку).

Випадок 4 взагалі самий складний - в нормальних умовах бібліотеки його не обробляють, оскільки для отримання повідомлень вікна, що не є об'єктом класу, необхідно підмінити процедуру обробки повідомлень.

Це накладає обмеження на застосування методів-обробників повідомлень - для вікон, не створених як об'єкти класу, ці методи викликатися не будуть. У разі MFC назви таких методів звичайно починаються на On..., наприклад OnDestroy; а у випадку OWL - на Ev..., наприклад EvDestroy. Часто можна так організувати додаток, що перевизначити ці методи просто не зажадається, однак це не завжди зручне і можливе.

При необхідності як-небудь змінити реакцію вікна на зовнішні події (перевизначити прийняту обробку повідомлень) треба, по-перше, створити відповідний об'єкт класу (як у випадку 2). По-друге звичайне вікно, що створюється Windows (наприклад, який-небудь елемент управління діалогом - кнопка, прапорець і пр.) або іншим додатком, використовує власну віконну процедуру. Ця процедура, природно, ніяк не пов'язана з бібліотекою ООП, вживаною вашим додатком. Таким чином, при отриманні вікном повідомлень, викликається тільки лише його власна віконна процедура, не обіговій до методів класу. Тобто необхідно здійснити підміну віконної процедури (в Windows це називається породженням підкласу вікон - subclass) за допомогою спеціальних методів бібліотек, що виконують цю операцію: SubclassWindowFunction в OWL або SubclassWindow в MFC. Після цього нова віконна функція буде звертатися до методів класу для обробки повідомлень, а як стандартна обробка буде використовуватися та віконна функція, яка використовувалася вікном до її підміни.

Однак при використанні цього прийому необхідно враховувати наступний нюанс:

- при створенні об'єкта класу краще використати один з базових класів (CWnd або TWindow), оскільки всі породжені від них класи перевизначити значно більше число методів, передбачаючи стандартну обробку повідомлень, реалізовану в DefWindowProc, а не в тій процедурі, яку ви підмінили. Це може привести до конфліктів між новою обробкою подій і колишньою віконною процедурою. Особливо небезпечна помилка в призначенні класу - бібліотека класів і компілятор ніяк не зможуть перевірити вас і попередити, якщо ви, скажемо, для кнопки, створите об'єкт класу "список" (LISTBOX). При такій помилці конфлікт практично неминучий. У будь-якому випадку треба добре уявляти собі, для якої стандартної віконної процедури реалізований який клас бібліотеки ООП і обробку яких повідомлень він перевизначити, перш ніж зважитися на підміну віконної процедури.

- у випадку Win32 для вікон, створених іншим додатком, віконні процедури (що використовується вікном і що призначається вами) розміщується в різних адресних просторах різних процесів. Звертання з іншого процесу за новою адресою функції приведе, швидше усього, до помилки - оскільки ця адреса заданий в адресному просторі вашого додатку, а що знаходиться в адресному просторі іншого процесу за цією адресою вам невідомо. Вирішити цю проблему можна, виділяючи опис об'єкта класу і його процедури в окрему DLL, а потім впроваджуючи її в адресний простір процесу, що створив вікно. Однак цей прийом істотно складніше.

Приклад 1C - використання власних класів

В цьому прикладі використовується трохи спрощений метод реалізації об'єктів. Головне обмеження - неможливість призначення обробників повідомлень для вікон, не створених як об'єкти класу. У іншому цей варіант зберігає всі необхідні функції, причому робить це більш компактним і швидким способом. Такий спосіб часто застосовується в додатках-прикладах, супроводжуючих компілятори.

Коротко розглянемо реалізацію цього способу: замість ведіння таблиць відповідності хендлов об'єктам додатку можна зберігати необхідні дані безпосередньо в структурі опису вікна в Windows (див. "Реєстрація класу вікон"). Оскільки доступ до цих даних здійснюється тільки за допомогою функцій, то розміщувати там весь опис вікна недоцільно, зате в цій структурі можна розмістити покажчик на пов'язаний об'єкт. Звідси слідує обмеження - цей метод буде працювати тільки з тими вікнами, в структурі опису яких в Windows зарезервоване спеціальне поле для покажчика. Це можуть бути тільки вікна, створені нами.

Малюнок 7. Пошук методу-обробника повідомлення в прикладі.

Крім цього використовується ще один прийом - замість таблиць функцій-обробників повідомлень для кожного класу вікон формується спеціальна віртуальна функція-диспетчер, яка здійснює виклики потрібних методів. Якщо у випадку MFC або OWL треба вести таблиці відгуку, то в прикладі, що розглядається треба розробляти відповідну функцію.

Крім того, для спрощення в прикладі залишилися деякі сліди звичайного програмування - залишилася, хоч і сильно змінена, функція WinMain, в якій створюється об'єкт "додаток".

Приклад, що Розглядається складається з 3х файлів: 1c.h - загальний заголовний файл, вмісний описи базових класів; 1c_cls.cpp - методи і статичні дані базових класів; 1c_main.cpp - власне сам додаток: опис власних класів і їх методів, а також функція WinMain.

Файл 1c.h

#define STRICT

#include < windows.h >

#define UNUSED_ARG(arg) (arg)=(arg)

class Win0 {

protected:

HWND hwnd;

virtual LRESULT dispatch(UINT, WPARAM, LPARAM);

virtual BOOL OnCreate(LPCREATESTRUCT);

virtual void OnDestroy(void) = 0;

virtual void OnPaint(HDC hdc) = 0;

public:

Win0(void);

~Win0(void);

BOOL create(char*);

void destroy(void);

void update(void) { UpdateWindow(hwnd); }

void show(int nCmdShow) { ShowWindow(hwnd, nCmdShow); }

friend LONG WINAPI _export Win0proc(HWND, UINT, WPARAM, LPARAM);

};

class App0 {

public:

static HINSTANCE hInstance;

static HINSTANCE hPrevInstance;

static LPSTR lpszCmdLine;

static int nCmdShow;

App0(HINSTANCE, HINSTANCE, LPSTR, int);

~App0(void);

BOOL init(void);

int run(void);

void release(void);

};

Файл 1c_cls.cpp

#"include 1c.h"

HINSTANCE App0::hInstance;

HINSTANCE App0::hPrevInstance;

LPSTR App0::lpszCmdLine;

int App0::nCmdShow;

static char szWndClass[]= test window class";

static Win0* on_create_ptr;

Win0::Win0(void)

{

hwnd = NULL;

}

Win0::~Win0(void)

{

destroy();

}

LRESUL WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

Win0* pwin;

pwin = (Win0*)GetWindowLong(hWnd, 0);

if (!pwin) {

SetWindowLong(hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr));

pwin- > hwnd = hWnd;

}

return pwin- > dispatch(uMsg, wParam, lParam);

}

LRESULT Win0::dispatch(UINT uMsg, WPARAM wParam, LPARAM lParam)

{

PAINTSTRUCT ps;

switch (uMsg) {

case WM_CREATE:return OnCreate(LPCREATESTRUCT)(lParam)? 0L: -1L;

case WM_PAINT: OnPaint(BeginPaint(hwnd, &ps)); EndPaint(hwnd, &ps); return 0L;

case WM_DESTROY:OnDestroy(); return 0L;

default: break;

}

return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

void Win0::destroy(void)

{

if (IsWindow(hwnd)) DestroyWindow(hwnd);

hwnd = (HWND)NULL;

}

BOOL Win0::create(char* title)

{

on_create_ptr = this;

CreateWindow( szWndClass, // class name

title, // window name

WS_OVERLAPPEDWINDOW, // window style

CW_USEDEFAULT, CW_USEDEFAULT, // window position

CW_USEDEFAULT, CW_USEDEFAULT, // window size

NULL, // parent window

NULL, // menu

hInstance, // current instance

NULL // user-defined parameters

);

on_create_ptr = (Win0*)NULL;

return IsWindow(hwnd);

}

BOOL Win0::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

UNUSED_ARG(lpCreateStruct);

return TRUE;

}

App0::App0(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpszCmd, int nShow)

{

hInstance = hInst;

hPrevInstance = hPrev;

lpszCmdLine = lpszCmd;

nCmdShow = nShow;

}

App0::~App0(void)

{

}

BOOL App0::init(void)

{

static BOOL done;

WNDCLASS wc;

if (!done && !)(hPrevInstance) {

wc.style = 0;

wc.lpfnWndProc = Win0proc;

wc.cbClsExtra = 0;

wc.cbWndExtra = sizeof(LONG);

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szWndClass;

done = RegisterClass(&wc)? TRUE: FALSE;

}

return done;

}

int App0::run(void)

{

MSG msg;

while (GetMessage(&msg, NULL, NULL, NULL)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

void App0::release(void)

{

}

Файл 1c_main.cpp

#"include 1c.h"

class MainWindow: public Win0 {

protected:

virtual void OnDestroy(void);

virtual void OnPaint(HDC hdc);

public:

MainWindow(void);

~MainWindow(void);

};

class MyApp: public App0 {

protected:

MainWindow wnd;

public:

MyApp(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow);

~MyApp(void);

BOOL init(void);

};

MainWindow::MainWindow(void): Win0()

{

}

MainWindow::~MainWindow(void)

{

}

void MainWindow::OnDestroy(void)

{

PostQuitMes MainWindow::OnPaint(HDC hdc)

{

TextOut(hdc, 0, 0, "Hello, world!, "13);

}

MyApp::MyApp(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow):

App0(hInst, hPrevInst, lpszCmdLine, nCmdShow)

{

}

MyApp::~MyApp(void)

{

}

BOOL MyApp::init(void)

{

if (App0::init()) {

if (wnd.create( "window header")) {

wnd.show(nCmdShow);

wnd.update();

return TRUE;

}

}

return FALSE;

}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

int a;

MyApp app(hInst, hPrevInst, lpszCmdLine, nCmdShow);

if (app.init()) {

а = app.run();

} else а = -1;

app.release();

return a;

}

Огляд прикладу 1C

Приклад містить два базових класи: App0 - описує додаток і Win0 - описує вікно.

Клас App0 містить 4 члена-даних: hInstance, hPrevInstance, lpszCmdLine і nCmdShow, які є аргументами функції WinMain. Цікавіше розібратися з методами, описаними в цьому класі. Конструктор просто ініціалізував членів-дані для використання в подальшому; деструктор взагалі нічого не робить. Пара методів init і release призначена для перевизначити надалі - метод init повинен виконувати специфічну ініціалізацію додатку, а метод release - операції при завершенні. У класі App0 метод init здійснює реєстрацію віконної процедури (в термінології Windows - класу), яка буде застосовуватися даним додатком. Метод run виконує цикл обробки повідомлень.

Клас Win0 містить тільки один член-дані hwnd - хендл вікна. Конструктор встановлює значення хендла вікна рівним NULL (вікно не створене), деструктор перевіряє існування вікна і, при необхідності, закриває його. Методи create, destroy, update і show відповідають функціям API: CreateWindow, DestroyWindow, UpdateWindow і ShowWindow. Методи OnCreate, OnDestroy і OnPaint відповідають обробникам повідомлень WM_CREATE, WM_DESTROY і WM_PAINT. Метод dispatch є диспетчером, який розподіляє повідомлення, що прийшли по відповідних методах-обробниках.

У тому-же класі декларирована дружня функція Win0proc, яка є власне віконною процедурою.

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

LONG WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

Win0* pwin;

pwin = (Win0*)GetWindowLong(hWnd, 0); // отримуємо покажчик на об'єкт

if (!pwin) { // покажчик рівний NULL - об'єкт тільки створюється

// ініціалізуємо об'єкт і покажчик на нього

SetWindowLong(hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr));

pwin- > hwnd = hWnd;

}

// викликаємо віртуальну функція-диспетчер

return pwin- > dispatch(uMsg, wParam, lParam);

}

При нормальній роботі перший виклик функції GetWindowLong поверне покажчик на об'єкт, так що наступний крок - виклик функції-диспетчера. Таким чином додаткові витрати ресурсів на реалізацію ООП таким способом виявляються мінімальними. У разі розробки класів-спадкоємців від Win0 треба розробити власну функція-диспетчер, яка буде замість процедури DefWindowProc викликати диспетчер класу-предка.

У прикладах, супроводжуючих компілятори ініціалізація об'єкта і покажчика на об'єкт в структурі опису вікна виконується при обробці WM_CREATE. Це рішення не є найкращим - повідомлення WM_CREATE далеко не саме перше з повідомлень, що обробляються, хоч, передбачивши обробку повідомлень з допомогою DefWindowProc при невизначеному покажчику, можна здійснювати ініціалізацію і при обробці WM_CREATE.

Звісно, цей приклад надто спрощений. Взагалі, навіть в найпростіших випадках, треба провести контроль коректності даних, пересвідчитися, що вікно ще не існує перед викликом CreateWindow в методі create, перевірити on_create_ptr перед використанням і багато що інше. Даний приклад спеціально позбавлений всього цього, що б в максимально відкритому вигляді продемонструвати найпростішу схему.

Основи роботи з пам'яттю

Додатково треба розібратися з декількома термінами Windows API, які постійно застосовуються, але дуже погано описані в документації. Мова йде про хендлах копію додатку (HINSTANCE), модуля (HMODULE) і задачі (HTASK). Все ці хендли використовуються різними функціями, причому різниця між ними ніяк не пояснюється. Крім цього в Win32 API з'явилася пара додаткових хендлов - хендл процесу і хендл потоку, а також ідентифікатори процесу і потоку. При цьому залишилася всі колишні поняття, що часто змінили значення, але в документації як і раніше не описані (або описані погано).

Хендл задачі (HTASK), хендли і ідентифікатори процесу і потоку 

У Windows 3.x під задачею мається на увазі конкретний запущений процес, для якого визначені командний рядок, поточний інструкція, що виконується, покажчик на стек, змінна оточення, PDB (еквівалент префікса задачі (PSP) в середовищі DOS) і пр. Хендл задачі можна отримати за допомогою функції

HTASK GetCurrentTask(void);

У Win32 хендл задачі не застосовується, а замість нього треба користуватися хендлами і/або ідентифікаторами процесу і потоку. Іхможнополучитьспомощьюфункций:

HANDLE GetCurrentProcess(void);

HANDLE OpenProcess(fdwAccess, fInherit, dwIDProccess);

DWORD GetCurrentProcessId(void);

HANDLE GetCurrentThread(void);

DWORD GetCurrentThreadId(void);

Функції GetCurrentProcess і GetCurrentThread повертають так званий псевдодескриптор [19] процесу (потоку). Псевдодескриптор - це деяка величина, що розглядається як дескриптор поточного процесу (потоку). Тобто ця величина, вживана в контексті іншого процесу (потоку), буде описувати його, а не даний потік. Для отримання "справжнього" хендла з псевдодескриптора треба скористатися функцією:

BOOL DuplicateHandle(SourceProcess, hSourceHandle, hTargetProcess, lphTargetHandle,

fdwAccess, fInherit, fdwOptions

);

Хендл копії додатку (HINSTANCE) 

У Windows 3.x цей хендл вказує на сегмент даних додатку, який містить стек і локальну купу. Для кожного запущеного додатку створюється свій власний сегмент даних, що дозволяє однозначно визначити конкретну копію додатку по його сегменту даних або організувати обмін даними між двома копіями одного додатку. Так функція GetInstanceData дозволяє скопіювати дані, належні сегменту даних іншої копії, в також саме місце поточної копії.

int GetInstanceData(hInstance, pByte, cbData);

У функцію WinMain передається як хендл поточної копії додатку, так і хендл попередньої копії, що дозволяє організувати взаємодію двох копій між собою, запобігти запуску інших копій, якщо це може привести до помилки, або для виконання дій, необхідних тільки в першій копії додатку.

У Win32 для кожного запущеного додатку (т. е. процесу) виділяється віртуальний адресний простір в 4Г в єдиному сегменті. Тому даний хендл відповідає не сегменту даних (який описує весь 4Г сегмент), а адресі у віртуальному просторі, з якого був завантажений даний модуль. У адресному просторі одного процесу ніяких інших додатків не існує, тому цей хендл не може застосовуватися для виявлення інших копій додатку і тим більше для обміну даними між різними копіями додатків. У додатках Win32 hPrevInstance завжди рівний NULL, а хендл поточної копії додатку в більшості випадків співпадає. При необхідності виявлення інших копій додатку треба використати які-небудь інакші методи, наприклад функцію:

HWND FindWindow(lpszClassName, lpszWindowTitle);

Хендл вікна в Win32 є унікальним і може ідентифікувати конкретне вікно в будь-якому додатку.

Для обміну даними між додатками (процесами) доводиться передавати дані з адресного простору одного процесу в адресний простір іншого. Для виконання цих операцій передбачене повідомлення WM_COPYDATA. Коли Ви посилаєте це повідомлення вікну, створеному іншим процесом, вказані Вами дані копіюються в адресний простір іншого процесу і можуть бути прочитані віконною процедурою вікна-одержувача. Цей механізм може застосовуватися і для обміну даними між 16ти і 32х бітовими додатками, однак для цього необхідно визначити номер повідомлення WM_COPYDATA і спеціальну структуру COPYDATASTRUCT для 16ти бітової платформи - оскільки файл windows.h не містить цих визначень:

#define WM_COPYDATA 0x004A

typedef struct tagCOPYDATASTRUCT {

DWORD dwData;

DWORD cbData;

LPVOID lpData;

} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;

Хендл модуля (HMODULE) 

У Windows 3.x під модулем розуміється окремий файл, що виконується або бібліотека динамічного компонування. Для опису модуля створюється спеціальний сегмент опису модуля, вмісний інформацію про всі сегменти даного модуля і їх атрибути. Хендл модуля ідентифікує цей сегмент. Для отримання хендла модуля Ви можете скористатися функціями:

HMODULE GetModuleHandle(lpszFileName);

int GetModuleFileName(hInstance, lpsBuffer, cbMaxSize);

У Windows 3.x хендл модуля часто може бути замінений на хендл копії додатку. У Win32 хендл модулі взагалі є синонімом хендла копії додатку. У документації ще зустрічаються обидва терміни, як вони перекочували з 16ти бітових Windows, хоч тепер вони тотожні.

Детальніше про додаток (2)

Отже, в попередніх розділах ми розглянули основи організації додатку в середовищі Windows. Це було перше знайомство з найпростішим додатком, який був лише мотивом для розмови про основи роботи віконних систем.

Додаток в середовищі Windows, як і в середовищі DOS, містить так звану головну функцію - WinMain, що викликається при запуску додатку. Додаток завершується при закінченні роботи цієї функції.

Звичайно, хоч це і не обов'язкове, функція WinMain реалізовує наступну схему:

- виконуються необхідні инициализационние дії

- створюється головне вікно додатку, для чого часто реєструється новий клас вікон (віконна функція);

- організується цикл обробки повідомлень додатку. Звичайно цикл завершується при закритті головного вікна додатку;

- після завершення циклу обробки повідомлень виконується звільнення зайнятих ресурсів, після чого функція WinMain закінчується.

Зауваження 1. Якщо додаток містить нетривалі (порядку 1 сік.) операції, що не вимагають взаємодії з користувачем (наприклад, тільки файл-орієнтований введення-висновок або настройка іншого додатку), то ці дії можуть бути виконані безпосередньо функцією WinMain без створення вікон і без організації циклу обробки повідомлень.

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

Зауваження 3. У момент виклику функції WinMain їй, через аргументи, передається декілька параметрів, наприклад хендл копії додатку hInstance. До виклику WinMain додаток "не знає" цих даних. Тому можуть виникати складності з використанням статичних конструкторів об'єктно-орієнтованих мов (З++).

!!!!!!!! Фокус введення!!!!!!

У Windows існує певна плутанина термінів. Спробуємо розібратися з деякими з них. Як відомо, вікно може знаходитися в декількох станах:

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

Для максимізації вікна ми можемо скористатися функцією ShowWindow з наступними можливими параметрами:

ShowWindow(hWnd, SHOW_FULLSCREEN);

ShowWindow(hWnd, SW_SHOWMAXIMIZED);

ShowWindow(hWnd, SW_MAXIMIZE);

максимізоване вікно завжди активно і має фокус введення. Коли яке-небудь вікно максимізувалося, всі інші верхні вікна отримують повідомлення WM_SIZE, що інформує про те, що вони "закриті" зверху максимізованим вікном.

Ми можемо взнати, чи є наше вікно максимізованим за допомогою функції

BOOL IsZoomed(hWnd);

При використанні системного меню операції максимізації вікна відповідає пункт Maximize, вибір якого породжує системну команду SC_MAXIMIZE (або синонім SC_ZOOM). (див. повідомлення WM_SYSCOMMAND)

Тут замість терміну maximize може використовуватися zoom.

Мінімізованим, тобто представленим у вигляді піктограми. Для того, що б перетворити вікно в піктограму, ми повинні скористатися одним з способів:

ShowWindow(hWnd, SHOW_ICONWINDOW);

ShowWindow(hWnd, SW_SHOWMINIMIZED);

ShowWindow(hWnd, SW_SHOWMINNOACTIVE);

ShowWindow(hWnd, SW_MINIMIZE);

CloseWindow(hWnd);

Різні способи, що використовують ShowWindow, відрізняються тільки правилами активації вікна. SW_SHOWMINIMIZED і SHOW_ICONWINDOW відображає вікно у вигляді піктограми, роблячи його активним; SW_SHOWMINNOACTIVE не змінює поточного активного вікна; SW_MINIMIZE (як і функція CloseWindow) робить активним наступне вікно в списку Windows. Останній спосіб ефективний при мінімізації головного вікна додатку - оскільки мінімізоване головне вікно звичайно означає передачу активності іншому додатку.

Перевірити стан вікна можна за допомогою функції

BOOL IsIconic(hWnd);

При використанні системного меню перетворенню вікна в іконку відповідає пункт Minimize, породжуючий системну команду SC_MINIMIZE (або синонім SC_ICON). (див. повідомлення WM_SYSCOMMAND)

У цьому випадку використовується відразу три різних терміни для позначення одного і того-же: minimize, close і iconic. При цьому функція CloseWindow є єдиною, що інтерпретує термін close таким способом; в інших випадках close означає дійсно закриття (іноді знищення) вікна. Тут же треба, що термін open, вживаний до мінімізованого вікна означає його максимізацію або відновлення нормальних розмірів.

Нормальним, тобто ми бачимо (або можемо побачити) його рамку, ми можемо переміщувати вікно по екрану. Коли вікно знаходиться в нормальному стані, то для нього визначені максимально і мінімально допустимий розміри. Ці розміри не можна плутати з максимізованим і мінімізованим станами. Максимальний розмір нормального вікна може навіть перевищувати розмір вікна в максимізованому стані, мінімальний розмір це звичайно такий розмір, при якому вікно ще може бути коректно представлене у вигляді вікна.

Для переходу з мінімізованого стану до нормального можна скористатися функцією

OpenIcon(hWnd);

або, як з мінімізованого, так і з максимізованого стану можна користуватися функцією ShowWindow з параметрами:

ShowWindow(hWnd, SHOW_OPENWINDOW);

ShowWindow(hWnd, SW_SHOWNORMAL);

ShowWindow(hWnd, SW_RESTORE);

ShowWindow(hWnd, SW_SHOWNOACTIVATE);

У документації (SDK Help) вказано, що SW_RESTORE і SW_SHOWNORMAL еквівалентні, але це далеко не так - SW_RESTORE відновлює попередній стан, а не нормальне. Тобто, якщо Ви мінімізували вікно з максимізованого, то SW_RESTORE поверне Вас до максимізованого вікна, а SW_SHOWNORMAL - до нормального. SW_SHOWNORMAL має синонім SHOW_OPENWINDOW.

Якщо вікно відновлюється або максимізувалося з мінімізованого стану, то Ваше вікно отримає повідомлення WM_QUERYOPEN - обробляючи яке Ви можете дозволити або заборонити подальші дії. Якщо Ви повертаєте TRUE, то вікно буде розкрите, а якщо Ви повернете FALSE, то вікно залишиться мінімізованим.

Декілька зауважень: Насправді Windows не є справжньою об'єктно-орієнтованою середою. Хоч вікно і може бути названо об'єктом ООП, але лише з достатньою натяжкою. Саме істотна відмінність вікна в Windows від об'єкта ООП полягає в тому, що повідомлення, що обробляється віконною функцією, в багатьох випадках не виконує дій, а є "інформаційним", вказуючи на те, що над вікном виконується та або інакша операція якою-небудь зовнішньою функцією.

Пояснимо це на прикладі створення вікна. У випадку ООП для знищення об'єкта він повинен отримати повідомлення "destroy", обробка якого приведе до його знищення. У Windows повідомлення WM_DESTROY не виконує ніяких функцій по знищенню вікна. Воно тільки інформує вікно про те, що в цей час вікно знищується коштами звичайної функціональної бібліотеки, наприклад за допомогою функції DestroyWindow. Ви можете взагалі ігнорувати це повідомлення, повертати будь-яке значення, викликати або не викликати функцію обробки за умовчанням - вікно все одно буде знищене.

Якби всі повідомлення, ті, що отримуються вікном були тільки інформаційними, то до цього легко можна було б пристосуватися. Однак для деяких повідомлень повинна виконуватися обробка за умовчанням, якщо Ви її не виконали самі, а для інших така обробка повинна виконуватися обов'язково, навіть якщо Ви вже обробили це повідомлення. Все це помітно ускладнює написання додатків в середовищі Windows.

Ситуація додатково посилюється тим, що в документації, як правило, нічого не повідомляється про те, яка обробка повідомлення виконується за умовчанням і, крім того, за деякими повідомленнями приводяться некоректні (або неповні) відомості про їх параметри, функціям, що виконуються, умовам виникнення і результаті, що повертається.

Для вікон, що використовують в якості процедури обробки повідомлень за умовчанням не DefWindowProc, а інакшу функцію (наприклад, DefMDIChildProc), можна уточнити список повідомлень обов'язково належних обробці за умовчанням. Однак це уточнення торкається тільки тих повідомлень, обробку яких для DefWindowProc можна ігнорувати, а для інакших функцій не можна, список же того, що можна ігнорувати для DefWindowProc, а що не можна залишається невідомим.

Настройка додатків

Під настройкою (іноді "профілюванням") розуміється завдання характеристик додатку і їх збереження для використання при наступному запуску.

Звичайно такі задачі вирішуються за допомогою створення настроювальних файлів. Однак конфігурація описується кожною задачею по-своєму, що не завжди зручно. Windows пропонує загальний для всіх додатків механізм опису їх характеристик, з використанням файлів настройки.

Такі файли (що звичайно мають розширення. INI) є звичайними ASCII-файлами, розділеними на секції, що починаються з імені секції, взятого в квадратні дужки. Далі слідує список параметрів у вигляді параметр=значення, кожний параметр розміщується в окремому рядку. У цей файл можна вставляти коментарі - рядки що починаються з ';'.

Приклад взятий з файла WORKSHOP.INI:

[User Controls]

BorShade=E:\BORLANDC\WORKSHOP\BWCC.DLL

[RWS_Bitmap]

PercentLeft=50

ZoomLeft=1

ZoomRight=1

роботи з такими файлами Windows надає набір функцій, що здійснюють запис і читання параметрів:

int GetProfileInt(lpszSection, lpszEntry, nDefault);

int GetProfileString(lpszSection, lpszEntry, lpszDefault, lpsBuffer, nMaxBuffer

);

BOOL WriteProfileString(lpszSection, lpszEntry, lpszString);

Параметр lpszSection задає ім'я секції (квадратних дужок в імені вказувати не треба), lpszEntry - ім'я параметра. Якщо ми набуваємо значення параметра, то можемо указати значення за умовчанням, яке повертається, якщо даний параметр не знайдений.

За допомогою функції GetProfileString можна отримати список імен всіх параметрів в секції, указавши lpszEntry рівним NULL. При цьому імена параметрів секції будуть скопійовані в буфер послідовно один за одним, кожне ім'я буде закінчуватися байтом ' \0' і після останнього імені будуть стояти два байти ' \0'.

Функция WriteProfileString дозволяє не тільки записувати параметри, але і видаляти, для чого треба указати lpszString рівним NULL. Можна видалити цілком всю секцію, указавши для цього lpszEntry рівним NULL.

Всі три розглянутих функції використовують файл WIN.INI. При цьому ім'я секції часто асоціюється з ім'ям додатку, тому в документації ім'я секції часто називається ім'ям додатку.

Звісно, часто буває незручно використати загальний файл настройок для всіх існуючих додатків (при цьому, зокрема, важко організувати видалення додатків). Windows надає можливість використати власний файл настройки. Для роботи з власними файлами настройки передбачені ще три функції:

int GetPrivateProfileInt(lpszSection, lpszEntry, nDefault, lpszIniFile);

int GetPrivateProfileString(lpszSection, lpszEntry, lpszDefault, lpsBuffer, nMaxBuffer, lpszIniFile

);

BOOL WritePrivateProfileString(lpszSection, lpszEntry, lpszString, lpszIniFile);

Останній параметр цих функцій lpszIniFile задає ім'я файла настройки. Якщо ви не вказуєте шлях до файла, то він розміщується в каталозі Windows.

Реєстр Windows

RegOpenKey Opens а specified key

RegCreateKey Creates а specified key

RegCloseKey Closes а key and releases the key's handle

RegQueryValue Retrieves the text string for а specified key

RegSetValue Associates а text string with а specified key

RegDeleteKey Deletes а specified key

RegEnumKey Enumerates the subkeys of а specified key

#include shellapi.h

LONG RegOpenKey(hkey, lpszSubKey, lphkResult);

HKEY hkey; /* handle of an open key */

LPCSTR lpszSubKey; /* address of string for subkey to open */

HKEY FAR* lphkResult; /* address of handle of open key */

The RegOpenKey function opens the specified key.

Parameter Description

hkey Identifies an open key (which can be HKEY_CLASSES_ROOT). The key opened by the RegOpenKey function is а subkey of the key identified by this parameter. This value should not be NULL.

lpszSubKey Points to а null-terminated string specifying the name of the subkey to open.

lphkResult Points to the handle of the key that is opened.

Returns

The return value is ERROR_SUCCESS if the function is successful. Otherwise, it is an error value.

Comments

Unlike the RegCreateKey function, the RegOpenKey function does not create the specified key if the key does not exist in the database.

Example

char szBuff[80];

LONG cb;

HKEY hkStdFileEditing;

if (penKey(EY_CLASSES_ROOT,

"NewAppDocument\\protocol\\StdFileEditing", == ERROR_SUCCESS

) {

cb = sizeof(szBuff);)(

if (ryValue(tdFileEditing,

"handler",

szBuff,

&cb

) == ERROR_SUCCESS

&& lstrcmpi( "nwappobj.dll", szBuff) == 0

) RegDeleteKey(hkStdFileEditing, "handler");)(

RegCloseKey(hkStdFileEditing);)(

}

[1] Оскільки процесор, звичайно, тільки один, то в даний момент часу буде працювати тільки один додаток.)( Однак, оскільки перемикання між додатками здійснюється досить швидко, то виникає враження одночасної роботи декількох додатків.)( Ця обмовка не впливає на подальші міркування.)(

[2] В принципі вимоги можуть і порушуватися.)( Так додатки під Windows 3.x порівняно легко можуть отримати доступ до апаратури, хоч робити це не рекомендується.)( Додаткам Win32 вже значно складніше отримати доступ - система краще захищена від таких спроб, особливо Windows NT.)(

[3] Суворо говорячи, звичайний DOS також може працювати з різними файловими системами - для CD-ROM дисків спеціально спроектована своя власна файлова система (CDFS).)( При цьому треба встановити драйвер CD-ROM, що забезпечує фізичний доступ до диска, і програму MSCDEX, яка здійснює роботу з дисками в форматі CDFS.)(

[4] Крім деяких особливих випадків, пов'язаних з обробкою критичних за часом процесів, скажемо деяких операцій введення/висновку, взаємодії з периферією і пр.)( Однак звичайно такі задачі вирішуються драйверами пристроїв, так що додаток про це знову-же не відає.)(

[5] Точніше при знищенні.)( Термін "закриття" в Windows часто має інше значення, в тому числі - згортання вікна в піктограму.)(

[6] В Win32 APIзаголовочний файл windows.h просто включає в себе набір директив #include для включення необхідних заголовних файлів і директив умовної компіляції.)(

[7] Рядок ASCII - рядок символів таблиці ASCII, тобто звичайний текст.)( Однак при програмуванні часто використовуються рядки або зі спеціальним завершальним символом (в З це байт з кодом 0 - ASCIIZ), або з вказівкою довжини рядка у) вигляді лідируючого байта (ASCIIB) або слова (ASCIIW).

[8] При необхідності з'ясувати наявність інших копій додатку в системі можна спробувати знайти інші вікна, належні тому-же класу, що і головне вікно нашого додатку. Це легко робиться за допомогою функції FindWindow. Цей метод працює як в Windows API, так і в Win32 API.

[9] При завданні хендла вікна-родителя вікна можуть знаходитися або у відносинах батьківське/дочірнє (parent/child), або у відносинах власник/використовуючий (owner/owned), в залежності від наявності стилю WS_CHILD у породженого вікна.

[10] У деякому керівництві в найпростіших прикладах обходяться без трансляції взагалі. Однак це є не зовсім коректним, оскільки функція TranslateMessage розпізнає комбінацію клавіш Alt+Space як команду натиснення на кнопку системного меню. Звісно без неї додаток буде працювати, але не в повній мірі реалізовує стандартний клавіатурний інтерфейс.

[11] У Windows всі покажчики, які отримує або передає Вам система є 32х розрядними. У випадку Windows API це будуть дальні (far) покажчики, а у випадку Win32 це ближні (near), оскільки вони вже є 32х розрядними.

[12] У перших версіях Windows внаслідок об'єднання формувався новий невірний прямокутник. У сучасних версіях замість невірного прямокутника реально формується невірний регіон, який може мати складну форму.

[13] Для перевірки можливостей апаратури потрібно використати функцію GetDeviceCaps.

[14] У окремому випадку - з верхнім лівим кутом самого вікна, включаючи його обрамлення (див. опис функції GetWindowDC).

[15] Крім випадків застосування контекстів, що зберігаються. См. стилі класу вікон CS_OWNDC і CS_CLASSDC.

[16] Панель діалогу є батьківським вікном для всіх елементів управління, розміщених на цій панелі, включаючи статичні - рамки, текст і інше.

[17] Орієнтувально, починаючи з компіляторів для Windows 3.0

[18] Це пов'язано з тим, що параметр wParam має тип UINT - тобто він в Windows API являє собою 16ти розрядне слово, а в Win32 API - 32х розрядне. Крім того, в молодшому або старшому слові lParam часто розміщується хендл, який в Windows API 16ти розрядний. На жаль в Win32 API хендли 32х розрядні, так що умістити його на колишньому місці не представляється можливим.

[19] Це просто деяка константа, що використовується для позначення «поточного потоку» або «поточного процесу».