Програмування для Windows
Ну що ж, DirectX встановлено, ваш компілятор налаштований і ви готові вирушити в дорогу! Зачекайте секундочку, мій друг, є ще кілька речей на які слід звернути увагу перш ніж з головою зануритися в світ програмування рольових ігор.
Перш за все, є кілька пов'язаних з Windows тим, які я хочу обговорити. До цих тем відноситься вивчення потоків і багатопоточності, критичні секції і COM. Я вважаю, що ця інформація абсолютно необхідна для читання решті частини цієї книги (натяк, натяк).
Потоки і багатопоточність
Windows 95 познайомила програмістів з ідеєю використання багатозадачних систем (навіть незважаючи на те, що Windows не є істинно багатозадачного системою, оскільки використовує багатозадачність, в якій невеликі фрагменти безлічі програм виконуються по одному). Ідея полягає в тому, що у вас є кілька процесів (додатків) працюють одночасно, кожному з яких виділяється частина часу процесора (звана квантом часу - time slice).
Багатозадачність дозволяє також кожен процес розділити на кілька окремих підпроцесів, званих потоками (threads). У кожного потоку є своя задача, наприклад, сканування мережевих даних, обробка призначеного для користувача введення або відтворення звуку. Використання декількох потоків в одному додатку називається багатопоточність (multithreading).
Створення додаткових потоків всередині вашого застосування не складає труднощів. Щоб створити потік ви створюєте функцію (використовуючи спеціальний прототип функції) містить код, який ви хочете виконати. Прототип, який використовується для функції потоку, виглядає так:
Параметр lpParameter - це визначений користувачем покажчик, який ви надаєте, коли створюєте потік за допомогою виклику функції CreateThread:
Що повертається функцією CreateThread значення є дескриптором, який при завершенні роботи повинен бути закритий, або системні ресурси не будуть звільнені. Звільнення використовуваних потоком ресурсів виконується шляхом виклику функції CloseHandle.
Це складна функція і я не буду заглиблюватися в деталі її роботи, а замість цього наведу приклад, який ви можете використовувати в якості зразка. У прикладі показана проста функція потоку і виклик для її ініціалізації:
Представлений код створює потік, виконання якого починається відразу після завершення роботи функції CreateThread. Що створює потік функції передається покажчик на змінну типу BOOL. яка відстежує стан потоку; прапор дозволяє визначити активність потоку і зберігає значення TRUE (потік активний) або FALSE (потік не активний).
Коли виконання потоку завершено, ви сигналізуєте про те, що потік більше не є активним (записавши значення FALSE в згадану раніше логічну змінну) і завершуєте роботу потоку викликом функції ExitThread. якої передається єдиний параметр - код завершення потоку, або, іншими словами, число, яке повідомить причину по якій виконання було завершено. У більшості випадків можна спокійно використовувати у виклику ExitThread значення 0.
Фактично, потік - це просто функція, виконувана одночасно з вашим додатком. Більш докладно про використання потоків ми поговоримо в розділі 4.
критичні секції
Оскільки Windows - це багатозадачна система, окремі додатки можуть заважати один одному, особливо програми, що використовують кілька потоків. Що якщо один потік заповнює структуру, яка містить важливі дані, а інший потік в цей же час хоче змінити або прочитати ці дані?
Є спосіб гарантувати, що коли необхідно, тільки одному потоку буде наданий повний контроль, і цей спосіб - використання критичних секцій (critical sections). При активації критична секція блокує всі інші потоки, які намагаються отримати доступ до пам'яті, що (пам'яті програми, яку використовують всі потоки), тим самим дозволяючи потоку в поодинці змінювати дані додатки, не турбуючись про можливість втручання інших потоків. Щоб використовувати критичну секцію ви повинні спершу оголосити і форматувати її:
Після цього ви можете увійти в критичну секцію, обробити дані і покинути критичну секцію, як показано в пріведенномніже прикладі:
Якщо критична секція вам більше не потрібна (наприклад, коли додаток завершує роботу), ви повинні звільнити її за допомогою виклику:
Хоча про критичні секціях можна розповісти і більш докладно, реальної необхідності в цьому немає. Використовувати їх досить легко і вони потрібні для багатопоточних додатків. Необхідно пам'ятати лише одне правило - переконайтеся, що знаходиться в критичній секції код виконується швидко; ви блокуєте системні процеси і, якщо ваша програма виконується занадто довго, це може привести до краху системи.
Використання COM
Використовуючи COM, ви створюєте програмні компоненти таким чином, щоб їх функціональність була сумісна з усіма програмами. Візьмемо, наприклад, Internet Explorer. Готовий посперечатися, ви не знаєте, що панель інструментів і вікно браузера є COM-об'єктами. Більш того, ви можете використовувати ці об'єкти в своїх додатках!
Хоча це і вагома причина для того, щоб почати використовувати COM, більш вагомою причиною є DirectX; DirectX складений виключно з COM-об'єктів.
ініціалізація COM
Для того, щоб використовувати COM-об'єкти необхідно ініціалізувати систему COM. Для ініціалізації COM застосовуються такі дві функції:
Обидві ці функції виконують поставлене завдання, але якщо ваше додаток багатопотокове, то ви повинні використовувати другу функцію, CoInitializeEx. оскільки необхідно вказати прапор COINIT_MULTITHREADED в параметрі dwCoInit. для того щоб система COM функціонувала правильно.
Коли ви завершуєте використання системи COM, необхідно вимкнути її викликом функції CoUninitialize. не має параметрів:
Кожному викликом CoInitialize або CoInitializeEx повинен відповідати окремий виклик CoUninitialize. Якщо ви викликаєте CoInitialize двічі (це допускається), вам необхідні два виклики CoUnitialize. Це ілюструє наступний фрагмент коду:
IUnknown - це базовий клас для всіх COM-інтерфейсів. Він містить тільки три функції: AddRef. Release і QueryInterface. AddRef виконує, якщо необхідно, ініціалізацію, і збільшує лічильник, що показує скільки разів був створений екземпляр класу. Ви повинні зробити так, щоб значення лічильника посилань відповідала кількості викликів функції Release. звільняє ресурси, використовувані екземпляром об'єкта.
Не плутайте класи, інтерфейси і об'єкти! Об'єкт - це екземпляр класу. Інтерфейс - це набір функцій, що надаються об'єктом (іншими словами, інтерфейси дозволяють взаємодіяти з об'єктом).
Третю функцію, QueryInterface. ви використовуєте щоб отримати доступ до надаваних об'єктом інтерфейсів, включаючи їх нові версії. Така ситуація може мати місце, коли було випущено кілька версій об'єктів, як в разі DirectX. Ви можете як і раніше використовувати старі інтерфейси, а щоб отримати доступ до нових, необхідно запросити їх. Якщо новий інтерфейс існує, об'єкт поверне покажчик; в іншому випадку QueryInterface повертає NULL. що свідчить про відсутність інтерфейсу або про помилку.
Необхідно, щоб функція повертала значення типу HRESULT. повідомляє про помилку або про успішне завершення. Щоб отримати будь-яке значення з COM-об'єкта ви передаєте покажчик на змінну (яка повинна бути словом або подвійним словом - байти і інші типи тут не підтримуються) в функцію і використовувати цей покажчик для повернення значення, що знаходиться всередині об'єкта.
Як приклад створимо простий об'єкт (успадкований від IUnknown), який отримує два числа, складає їх і поміщає результат в змінну, зазначену в третьому параметрі:
Ініціалізація і звільнення об'єктів
Щоб використовувати COM-об'єкт ви повинні (крім написання завантажується Windows бібліотеки) створити його за допомогою функції CoCreateInstance:
Всі створені вами COM-об'єкти повинні бути в кінцевому рахунку звільнені. Для цих цілей призначена функція IUnknown :: Release без параметрів:
Після того, як ви завершите роботу з інтерфейсом IAdd2. необхідно звільнити його наступним чином:
запит інтерфейсів
Одна з найкращих особливостей COM - зворотна сумісність. Якщо у вас з'явиться новий COM-об'єкт (що містить нові інтерфейси), то залишиться повний доступ через об'єкт до старих інтерфейсів. Збереження старих інтерфейсів гарантує, що код буде правильно працювати, навіть якщо кінцевому користувачеві встановили нові версії COM-об'єктів. Це також означає, що старі інтерфейси можуть запитувати нові інтерфейси.
Це робиться за допомогою методу IUnknown :: QueryInterface:
Оскільки вихідний об'єкт, що викликає функцію запиту вже створений, тут не треба турбуватися про надання ідентифікатора класу - досить вказати тільки ідентифікатор необхідного інтерфейсу. Повернемося до об'єкту класу Math і припустимо, що ви хочете отримати інтерфейс IAdd і потім через нього запросити інтерфейс IAdd2:
Хоча COM - це велика тема, наведеної інформації досить для початку використання DirectX. Ви дізнаєтеся більше про DirectX в інших розділах книги, так що давайте змінимо напрямок і поговоримо про інші важливі для проекту речах - про потік виконання програми.