Введення у Win32 API. Розбираємось у WinAPI Функцій windows api для c

Нарешті! Нарешті! Сьогодні ми почнемо створювати повноцінне вікно Windows. Прощай убога консоль!

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

Угорська форма запису

Весь код, який ми зустрінемо у WinAPI написаний у угорській формі. Це така угода щодо написання коду.

У цьому перед ім'ям змінної ставиться початкова літера типу. Всі слова в іменах змінних та функцій починаються з великої літери.

Ось кілька префіксів:

b – змінна типу bool.
l – змінна типу long integer.
w – від word (слово) – 16 біт. Змінна типу unsigned short.
dw – від double word (подвійне слово) – 32 біти. Змінна типу unsigned long.
sz - рядок, що закінчується нулем (string terminated zero). Просто звичайний рядок, який ми постійно використовували.
p або lp – покажчик (від pointer). lp (від long pointer) – дані покажчики перейшли з минулого. Зараз lp і p означають те саме.
h – описувач (від handle).

Наприклад, покажчик називатиметься ось так:

void * pData;

Ця форма запису використовується Microsoft. Багато хто критикує цей спосіб іменування змінних. Але подібні речі (угоди про кодування) у великих компаніях життєво потрібні.

Нагадаю, що ідентифікатори констант зазвичай складаються лише з великих літер: WM_DESTROY. WM_DESTOY - це 2, константа визначена через define.

Крім того, у winAPI використовується дуже багато перевизначених типів. Ось на цій сторінці - http://msdn.microsoft.com/en-us/library/aa383751(VS.85).aspx, можете знайти описи всіх типів Windows (англійською).

І ще одна річ, яку ми не розбирали. Покажчикам часто надається значення NULL. Вважайте, що це просто 0 і вказівники яким присвоєно значення NULL (нуль), не вказують на ділянку пам'яті.

Windows API (WinAPI)

Усі програми під Windows використовують спеціальний інтерфейс програмування WinAPI. Це набір функцій та структур на мові C, завдяки яким ваша програма стає сумісною із Windows.

Windows API має величезні можливості для роботи з операційною системою. Можна навіть сказати – безмежними.

Ми не розглянемо навіть один відсоток усіх можливостей WinAPI. Спочатку я хотів взяти більше матеріалу, але це зайняло б занадто багато часу, і загрузнувши в болоті WinAPI, до DirectX"а ми дісталися б через пару років. Опис WinAPI займе два уроки (включаючи цей). У них ми розглянемо тільки каркас програми під Windows.

Програма під Windows так само як і програма під DOS, має головну функцію. Тут ця функція називається WinMain.

Функція WinMain

Програма під Windows складається з наступних частин (все це відбувається всередині WinMain):

Створення та реєстрація класу вікна. Не плутайте із класами C++. WinAPI написана на C, тут немає класів у звичному для нас розумінні цього слова.
Створення вікна програми.
Основний цикл, у якому обробляються повідомлення.
Обробляє повідомлення програми у віконній процедурі. Віконна процедура є звичайною функцією.
Ось ці чотири пункти – основа програми Windows. Протягом цього та наступного уроку ми розберемо все це докладно. Якщо ви заплутаєтеся в описі програми, поверніться до цих пунктів.

Тепер розберемо все це докладно:

WinAPI: Структура WNDCLASS

Насамперед потрібно створити та заповнити структурну змінну WNDCLASS, а потім на її основі зареєструвати віконний клас.

Ось як виглядає ця структура:

код мовою c++ typedef struct ( UINT style; // стиль вікна WNDPROC lpfnWndProc; // покажчик на віконну процедуру int cbClsExtra; // додаткові байти після класу. Завжди ставте 0 int cbWndExtra; // додаткові байти після екземпляра вікна. Завжди ставте 0 / екземпляр програми: Передається у вигляді параметра у WinMain HICON hIcon; // іконка програми HCURSOR hCursor; // курсор програми HBRUSH hbrBackground; // колір фону LPCTSTR lpszMenuName; // ім'я меню LPCTSTR lpszClassName; ;

Структура WNDCLASS у складі WinAPI визначає базові властивості вікна, що створюється: іконки, вид курсора миші, чи є меню біля вікна, якому додатку буде належати вікно...

Після заповнення цієї структури на її основі можна зареєструвати віконний клас. Йдеться не про такі класи як у C++. Швидше можна вважати, що віконний клас це такий шаблон, ви його зареєстрували у системі, і тепер на основі цього шаблону можна створити кілька вікон. І всі ці вікна будуть мати властивості, які ви визначили в структурній змінній WNDCLASS.

WinAPI: Функція CreateWindow

Після реєстрації віконного класу на його основі створюється головне вікно програми (ми зараз приступили до другого пункту). Робиться це за допомогою функції CreateWindow. Вона має наступний прототип:

код мовою c++ HWND CreateWindow(LPCTSTR lpClassName, // ім'я класу LPCTSTR lpWindowName, // ім'я вікна (відображається в заголовку) DWORD dwStyle, // стиль вікна int x, // координата по горизонталі від лівого краю екрана int y, // координата по вертикалі верхнього краю екрана int nWidth, // ширина вікна int nHeight, // висота вікна HWND hWndParent, // батьківське вікно HMENU hMenu, // описувач меню HINSTANCE hInstance, // екземпляр програми LPVOID lpParam // параметр; завжди ставте NULL);

Якщо віконному класі (структурі WNDCLASS) задаються базові властивості вікна, тут - специфічні кожному за вікна: розмір вікна, координати...

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

Зверніть увагу, що тут багато нових типів. Насправді всі вони старі, просто перевизначені. Наприклад: HWND - це перевизначення типу HANDLE, який своєю чергою є перевизначенням PVOID, що у своє чергу є перевизначенням void*. Як глибоко закопана правда! Але все ж таки тип HWND - це покажчик на void.

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

Це щодо віконного режиму. Досить довго ми практикуватимемося з DiectX саме у вікні - не будемо користуватися повноекранним режимом.

Обробка повідомлень (Message handling)

Основною відмінністю всіх наших попередніх програмвід програм під Windows є обробка повідомлень.

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

Тут у нас сталася подія (event) – була натиснута клавіша.

Подія може бути: переміщення курсору миші, зміна фокусу програми, натискання клавіші клавіатури, закриття вікна. Події дуже багато. Дуже! За секунду в операційній системі можуть відбуватися десятки подій.

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

Повідомлення може створювати як операційна система, так і різні програми.

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

код мовою c++ typedef struct tagMSG ( HWND hwnd; // вікно, яке отримає це повідомлення UINT message; // код повідомлення WPARAM wParam; // параметр LPARAM lParam; // параметр DWORD time; // час, коли відбулося повідомлення POINT pt; // координати курсора миші) MSG;

Зверніть увагу, як за допомогою typedef перевизначаються структури.

Щоб створити цю структуру, можна скористатися наступним кодом:

код мовою c++ msg.messgae == 2; // ці два рядки еквівалентні тому що msg.message == WM_DESTROY; // Константа WM_DESTROY дорівнює двом

Тут поле, в якому міститься код повідомлення (ім'я повідомлення, порівнюється з константою WM_DESTROY. WM - від Windows Message (повідомлення Windows). WM_DESTROY - це повідомлення, яке генерується при закритті вікна (destroy - знищити).

Коди повідомлень визначені за допомогою констант та мають префікс WM_: WM_CLOSE, WM_CREATE та ін.

У структурі MSG зустрічається тип HWND – від Window Handle (дескриптор вікна або описувач вікна). Це така штука, яка "описує" вікно. Це щось на зразок ідентифікатора (імені вікна).

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

Всі взаємодії між програмами в Windows здійснюються за допомогою цих описувачів вікон (HWND).

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

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

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

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

Віконна процедура (Window procedure - WndProc)

Продовжуємо з того моменту, як повідомлення потрапило до черги повідомлень програми. Щойно до нього дійшла черга, воно обробляється. Для обробки повідомлень у кожній програмі має бути спеціальна функція - віконна процедура. Зазвичай вона називається WndProc (від Window Procedure). Виклик віконної процедури розташований в основному циклі програми та виконується при кожній ітерації циклу.

Повідомлення (у вигляді структурних змінних MSG) потрапляють у цю функціюу вигляді параметрів: описувач вікна, ідентифікатор повідомлення та два параметри. Зверніть увагу, що віконну процедуру не передаються поля time і pt. Тобто, повідомлення вже "розібране".

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

код мовою c++// не звертайте поки що уваги на HRESULT і __stdcall. Ми розглянемо їх пізніше. ) // обробник решти повідомлень )

І останнє – основний цикл. Він дуже простий. Кожну ітерацію циклу перевіряється черга повідомлень програми. Якщо у черзі повідомлень є повідомлення, воно витягується з черги. Потім у тілі циклу відбувається виклик віконної процедури, щоб обробити повідомлення з черги.

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

Як вправу створіть новий проект. У вікні New Project (Новий проект) виберіть шаблон (template) – Win32Project (досі ми вибирали Win32 Console Application). В одному з таких вікон не ставте прапорець Empty Project (порожній проект) і IDE згенерує заготівлю програми.

Якщо ви уважно подивитеся на код файлу имя_проекта.cpp, то ви виявите всі речі, які ми обговорювали: структурну змінну MSG, заповнення структури WNDCLASS, створення вікна функцією CreateWindow, основний цикл програми. Крім того, у файлі визначено функцію WndProc. У ній відбувається обробка кількох повідомлень у гілках switch: WM_COMMAND, WM_PAINT, WM_DESTROY. Знайдіть все це у файлі.

Крім того, що ми розглянули, у програмі міститься багато додаткового коду. У наступному випуску ми розглянемо код програми, де буде вирізано все зайве. Він буде набагато простіше і зрозуміліше того, що генерує IDE.

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

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

Оголошення функцій Windows API у Visual FoxPro

Функції Windows API скомпоновані в бібліотеки, що динамічно пов'язані (Dynamic Link Library, DLL). Як правило, файли таких бібліотек мають розширення dll. Перед тим, як використовувати функцію Windows API у вашому додатку, ви повинні її оголосити. Для оголошення функції застосовується команда DECLARE..DLL:

DECLARE [ cFunctionType] FunctionName IN LibraryName ; [cParamType1 [@] ParamName1, cParamType2 [@] ParamName2, ...]

Параметри команди:

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

cFunctionType Розмір,
байт
Опис
Short 16-ти розрядне ціле число
Integer, Long 4 32-х розрядне ціле число
Single 4 32-х розрядне речове число
Double 8 64-х розрядне речове число
String - Рядок символів

FunctionName
ім'я функції у DLL-бібліотеці. Ім'я функції чутливе до регістру символів, тобто GetDC та GETDC – це імена абсолютно різних функцій.

LibraryName
найменування DLL-бібліотеки, у якій перебуває функція. Для бібліотек Kernel32.dll, Gdi32.dll, User32.dll, Mpr.dll та Advapi32.dll можна використовувати синонім WIN32API.

AliasName
необов'язковий параметр, що дозволяє замість імені функції використовувати придуманий вами псевдонім. Написання псевдоніма, на відміну імені функції, не чутливе до регістру символів. Як правило, псевдонім використовується, коли ім'я функції API збігається з ім'ям вбудованої (або вашої) функції Visual FoxPro.

cParamType
вказує тип даних функції функції, що передається:

Параметр може передаватися як за значенням, і за посиланням. Для вказівки на те, що параметр передається за посиланням, використовується символ "@".

З погляду Visual FoxPro немає різниці між типами даних Long і Integer. Зазвичай тип Integer застосовується позначення цілих чисел зі знаком, а тип Long - цілих чисел без знака.

ParamName
необов'язковий параметр, що носить суто описовий характер і, як правило, ігнорується.

Усі функції Windows API, як, втім, і Windows, написані мовою програмування Сі. Тому для того, щоб зрозуміти, як правильно використовувати API функції в Visual FoxPro (який, до речі, так само написаний на Сі, принаймні його ядро), познайомимося, які типи даних застосовуються в Сі і Windows, і, що не менше важливо, розберемося з такими типами даних, як перерахування, структури та покажчики. Крім того, ви дізнаєтеся, що таке прототипи функцій в Сі, і як, спираючись на опис прототипу функції в MSDN, правильно оголосити її в команді DECLARE..DLL.

Базові типи даних Сі

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

Typedef int INT32;

і ось у вас новий тип INT32 який повністю відповідає типу int. Але, з погляду Сі, це зовсім різні типи, і спроба присвоїти змінної типу INT32 значення змінної типу int призведе до помилки!

Достаток типів даних змушує багатьох розробників думати, що програмування з допомогою API є важким. Але це не так! У Сі переважно використовуються такі типи даних:

    тип char - Символ у форматі ANSI. Має довжину 8 розрядів (один байт).

    тип wchar - Символ у форматі Unicode. Має довжину 16 розрядів (два байти).

    тип int - цілі числа. Вони діляться в Сі на три типи: int, short intі long int. Останні зазвичай скорочуються до shortі long. Тип short- це 16-ти розрядне, а типи intі long- 32-х розрядні цілі числа.

    тип float- Речові числа, що мають дробову частину. Мають довжину 32 розряди (4 байти).

    тип double- Речові числа подвійної точності. Мають довжину 64 розряди (8 байт).

    тип enum - тип даних, що перераховується.

    тип void використовується для позначення величин, що мають нульову довжину та не мають значення.

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

Як не дивно, рядковий тип Сі відсутній. Насправді всі рядки представлені Сі як масиви символів.

Деякі типи можуть бути оголошені як беззнакові. Модифікатор unsigned (без знака) використовується з наступними типами даних: char, short, intі long.

Наприклад, наступне оголошення змінної в Сі:

Usigned int ім'я_змінної;

означає, що це змінна - ціле 32-х розрядне ціле без знака.

Модифікатор const показує, що змінна зазначеного типу є константою, тобто її значення може бути змінено.

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

Enum поле_тега { const1, const2, ... } змінна;

Якщо поле_тегаопускається, то після закриття фігурної дужки необхідно вказати змінну. Якщо поле_тегавказано, то не вказується змінна.

Історично склалося так, що тип enum рівнозначний типу int - тобто змінна типу, що перераховується займає в пам'яті 4 байти. Кожна константа, що перераховується, має значення, що визначається її порядковим номером у списку; нумерація починається з нуля. Розглянемо перелік CombineMode:

Enum CombineMode( CombineModeReplace, CombineModeIntersect, CombineModeUnion, CombineModeXor, CombineModeExclude, CombineModeComplement );

У цьому переліку константа CombineModeReplace має значення 0, константа CombineModeIntersect має значення 1, і так далі; Константа CombineModeComplement має значення 5.

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

Enum DashCap (DashCapFlat = 0, DashCapRound = 2, DashCapTriangle = 3);

Перелічені типи даних покривають 99% всіх типів даних, які у програмуванні Windows API. Це звучить дуже просто, чи не так? Чому ж описи API функцій містять усі ці типи - HWND, HINSTANCE, POINT та подібні до них?

Причиною тому є те, що Cі має особливість, яку називають strict-typing. Одного разу, змінна одного типу може приймати тільки ті значення, які відповідають її типу. Ви не можете спочатку зберегти в змінному рядку, а потім надати їй число. У Visual FoxPro ми зазвичай намагаємось симулювати подібне шляхом угоди про найменування. Наприклад, cName є змінною символьного типу, тоді як nCount - числову. Strict-typingдозволяє створити новий тип даних, надавши існуючому типу даних нове ім'я. Кожен новий тип представляється відмінним від інших типів, незважаючи на те, що внутрішньо вони зберігають те саме.

Windows ускладнює використання цієї концепції. Наприклад, тип LONG насправді є long int, а тип UINT - unsigned int. Обидва типи є 32-розрядними цілими числами. Похідні типи даних визначаються у різних include-файлах (файли з розширенням.h). Якщо ви придбали Visual Studio.NET, можете знайти ці файли в папці ..\VC7\PlatformSDK\Include\.

Типи даних Windows

Визначення того, який з базових типів Сі дійсно представляє тип даних, що використовується в функції API, є одним з найважчих завдань у програмуванні API. Використовуйте таке правило: якщо ви не можете знайти слово float, double, charабо strбудь-де в імені функції або параметра, тоді це зазвичай 32-розрядне ціле. Потрібен деякий час для розуміння та вироблення навичок, але потім ви будете запросто перетворювати типи даних. У наступній таблиці наведено основні типи даних Windows та відповідні їм типи, що використовуються при оголошенні функції Visual FoxPro:

Тип даних
Windows
Тип в оголошенні
функції
Опис
BOOL Long 32-х розрядне ціле число. 0 означає false, решта означає true.
BOOLEAN Long те саме, що і BOOL.
BYTE String 8-ми розрядне ціле число
CHAR String 8-ми розрядне ціле число
CLSID String
COLORREF Long 32-х розрядне ціле число
DWORD Long 32-х розрядне ціле число
DOUBLE Double 64-х розрядне речове число
FLOAT Single 32-х розрядне речове число
GUID String 128-розрядне число (16 байт)
HANDLE Long
HBITMAP Long 32-х розрядне ціле число без знаку
HDC Long 32-х розрядне ціле число без знаку
HICON Long 32-х розрядне ціле число без знаку
HGLOBAL Long 32-х розрядне ціле число без знаку
HKL Long 32-х розрядне ціле число без знаку
HLOCAL Long 32-х розрядне ціле число без знаку
HINSTANCE Long 32-х розрядне ціле число без знаку
HRESULT Long 32-х розрядне ціле число без знаку
HWND Long 32-х розрядне ціле число без знаку
LONG Long 32-х розрядне ціле число
LPARAM Long 32-х розрядне ціле число без знаку
SHORT Integer 16-ти розрядне ціле число
SIZE_T Long 32-х розрядне ціле число без знаку
TCHAR String Відповідає типу CHAR для рядків формату ANSI та WCHAR для рядків формату Unicode
UCHAR String Символ в кодуванні ANSI
UINT Long 32-х розрядне ціле число без знаку
ULONG Long 32-х розрядне ціле число без знаку
USHORT Integer
UUID String 128-розрядне число (16 байт)
VOID ні Не має значення
WCHAR String UNICODE character
WNDPROC Long 32-х розрядне ціле число без знаку
WORD Integer 16-ти розрядне ціле число без знаку
WPARAM Long 32-х розрядне ціле число без знаку

Вказівники

Іншою концепцією, що широко використовується в Сі, є покажчики (pointers). Покажчик є змінною, яка містить адресу області пам'яті, за якою зберігаються дані. Тип покажчика завжди визначається типом даних, куди він вказує; його розмір завжди дорівнює чотирьом байтам. Наприклад, покажчик на змінну типу SHORT являє собою ціле число 32-розрядне, як і покажчик на будь-який інший тип даних. Опис покажчика, прийняте в програмуванні Windows API, починається з символів "LP", що означає Long Pointer, або "довгий покажчик", що працює з 32-розрядною моделлю пам'яті. Потім може бути символ "C" (const), що вказує, що дані не повинні змінюватися. Далі слідує опис типу даних змінної, адреса якої зберігається в покажчику. Наприклад, LPDWORD - покажчик на змінну типу DWORD.

Вказівники на числові дані при оголошенні Windows API функції передаються за посиланням. Як приклад, розглянемо функцію GetFileSize. Ось її прототип (докладніше про прототипи функцій буде розказано нижче):

DWORD GetFileSize(HANDLE hFile, // дескриптор файлу LPDWORD lpFileSizeHigh // покажчик);

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

Оголошення цієї функції у Visual FoxPro:

DECLARE GetFileSize IN WIN32API Long hFile, Long @ FileSizeHight

Як бачите, параметр FileSizeHight передається функції за посиланнямтому що передача за посиланням - це і є передача покажчика.

Складніше справа з рядками. Як мовилося раніше, символьні рядки в Cі - це масиви, у програмуванні API функцій застосовується тип str, визначальний масив символів типу CHAR (відповідно, тип wstrвизначає масив символів типу WCHAR. У наступній таблиці показано типи покажчиків на символьні рядки:

Тип вказівника
на рядок
Опис
LPSTR Вказівник на нуль-термінований рядок ANSI-формату, що модифікується. Передається за посиланням
LPCSTR Вказівник на нуль-термінований рядок ANSI-формату, що немодифікується. Передається за значенням
LPTSTR Відповідає типу LPSTR для рядків формату ANSI та типу LPWSTR для рядків формату UNICODE. Передається за посиланням.
LPCTSTR Відповідає типу LPSTR для рядків формату ANSI та типу LPWSTR для рядків формату UNICODE. Передається за значенням.
LPWSTR Вказівник на нуль-термінований рядок UNICODE, що модифікується. Передається за посиланням
LPCWSTR Вказівник на нуль-термінований рядок UNICODE, що немодифікується. Передається за значенням

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

Структури

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

Struct поле_тега { тип_елемента елемент1; тип_елемента елемент2; ..... тип_елемента елементN; };

Можливе і таке оголошення структури:

Struct ( тип_елемента елемент1; тип_елемента елемент2; ..... тип_елемента елементN; } змінна;

У цьому оголошенні відсутня поле тегата створюється так званий анонімний структурний тип; такий синтаксис дозволяє пов'язати з цим структурним типом одну або кілька змінних, як, наприклад, у наступному прикладі:

Struct (WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; ) SYSTEMTIME, * PSYSTEMTIME;

Структури дуже подібні до записів таблиць Visual FoxPro. Так, якщо запис таблиці personalмістить поля fio,address,tlfnumber та email, то для звернення до поля tlfnumber використовується наступний синтаксис:

Personal.tlfnumber

Також виглядає і звернення до поля структури:

SYSTEMTIME.wMinute

Для формування структур Visual FoxPro використовуються змінні, що містять рядки символів. Наприклад, для розглянутої вище структури SYSTEMTIME вам знадобиться змінна довжиною 16 байт. У перші два байти цієї змінної заноситься значення поля wYear, у наступні два байти - значення поля wMonth, у наступні два байти - значення поля wDayOfWeek, і так далі, поки структура не буде повністю сформована. А при оголошенні API функції Visual FoxPro тип параметра, в якому передається змінна, що містить структуру, повинен бути типу String. Як записати в рядкову змінну числові дані, ви дізнаєтеся трохи згодом.

При програмуванні Windows API на Сі опис покажчика на структуру починається з символів LP (Long Pointer), за якими слідує найменування структури. Так, покажчик нас структуру SYSTEMTIME матиме тип LPSYSTEMTIME, покажчик на структуру POINT матиме тип LPPOINT, тощо. Як бачите, нічого складного, але завдяки цій концепції існує надзвичайно велика кількість типів покажчиків на структури.

Якщо дані в структурі, що передається, не повинні змінюватися, то покажчик на таку структуру оголошується так:

CONST ім'я_стуктури *

Тут модифікатор CONST означає, що дані в структурі не повинні змінюватися, а символ (*) після імені структури означає, що весь цей рядок є описом покажчика на структуру. У цьому прикладі показаний прототип функції CopyRect, яка копіює одну структуру в іншу:

BOOL CopyRect(LPRECT lprcDst, CONST RECT * lprcSrc);

Опис прототипів функцій у MSDN

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

Відповідно до стандарту ANSI, всі функції Сі повинні мати прототипи. Прототип функції досить простий:

повертається_типім'я_функції( тип_параметра(ів) ім'я_параметра(ів));

Якщо у прототипі вказано тип VOID як повертається_тип, це означає, що функція не повертає жодних значень. Якщо тип VOID зазначено як тип_параметра, Це означає, що функція не має параметрів.

Інформацію про прототипи Windows API функцій, включених до бібліотек Kernel32.dll, Gdi32.dll, User32.dll, Mpr.dll і Advapi32.dll, у MSDN для Visual Studio.NET ви можете знайти, послідовно відкриваючи наступні розділи змісту (Contents) довідки :

MSDN Library

Windows Development Win32 API SDK Documentation Reference

У розділі Reference ви можете переглянути описи функцій, відкривши один із наступних підрозділів:

Ось ще одна адреса в MSDN, за якою також є інформація про API функції:

MSDN Library

User Interface Design and Development SDK Documentation Windows Shell

На наступному малюнку показано фрагмент вікна довідкової системи MSDN:

Ось як, наприклад, описана в MSDN функція CopyRect:

CopyRect

The CopyRectфункція копіювання coordinates of one rectangle to another.

BOOL CopyRect(
LPRECT lprcDst, // destination rectangle
CONST RECT* lprcSrc// source rectangle
);

Parameters

lprcDst
Pointer to the RECTСтруктура того, що отримується логічні coordinates of source rectangle.
lprcSrc
Поinter to RECT структури, які coordinates є для копіювання в логічні партії.

Return Values

Якщо функції виконані, повернення значення є nonzero.
Якщо функції fails, return value is zero.
Windows NT/2000/XP:Щоб отримати додаткову error information, call GetLastError.

Remarks

Оскільки застосування можуть використовувати rectangles для різних purposes, rectangle функцій не використовувати explicit unit of measure. Instead, всі rectangle coordinates and dimensions є given in signed, logical values. Мапування режиму і функції в яких rectangle використовується значення units of measure.

Example Code

For an example, see Using Rectangles.

Requirements

Windows NT/2000/XP: Included in Windows NT 3.1 and later.
Windows 95/98/Me: Included in Windows 95 and later.
Header: Declared in Winuser.h; include Windows.h.
Library: Use User32.lib.

Як бачите, інформація є досить вичерпною. Функція повертає значення типу BOOL, їй передаються два параметри: типу LPRECT та CONST RECT* – покажчики на структури типу RECT. При оголошенні цієї функції Visual FoxPro ви повинні вказати, що перший параметр передається за посиланням, а другий - за значенням:

DECLARE Long CopyRect IN User32.dll String @ Dst, String Src

Як я визначив, що ця функція знаходиться в бібліотеці User32.dll? Дуже просто. У розділі рекомендацій ( Requirements) пункт Library говорить: Use User32.lib. Підставте замість розширення libрозширення dll- і все! До речі, там, у пункті Header, повідомляється, в якому include-файлі міститься опис прототипу функції.

Але це ще не все! Оскільки функція працює зі структурами, то її описі є гіперпосилання на структуру RECT. Клацніть мишею за цим посиланням, і на екрані з'явиться докладний опис структури.

Формування структур у Visual FoxPro

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

Синтаксис функції BINTOC:

BINTOC( nExpression, eFlag)

З усіх можливих значень, які можуть приймати параметр eFlag, нас цікавлять наступні:

Синтаксис функції CTOBIN:

CTOBIN(c Expression, eFlag)

Можливі значення параметра eFlag:

Нижче наведено приклади використання цих функцій:

CTOBIN(BINTOC(1000.55,"4RS"), "4RS") && Результат: 1000 ? CTOBIN(BINTOC(-1000.55,"4RS"), "4RS") && Результат: -1000 ? CTOBIN(BINTOC(1000.55,"F"), "4N") && Результат: 1000.549987929 ? CTOBIN(BINTOC(-1000.55,"F"), "4N") && Результат: -1000.549987929 ? CTOBIN(BINTOC(1000.55,"B"), "8N") && Результат: 1000.55 ? CTOBIN(BINTOC(-1000.55,"B"), "8N") && Результат: -1000.55

Як приклад запишемо в змінну Visual FoxPro структуру RECT. Ця структура використовується у розглянутій раніше функції CopyRect для опису координат прямокутної області. Ось як ця структура описується в MSDN:

Typedef struct _RECT ( LONG left; LONG top; LONG right; LONG bottom; ) RECT, *PRECT;

Як бачите, структура RECT містить чотири поля, у кожному з яких зберігається значення типу LONG. Для її формування Visual FoxPro знадобиться рядок довжиною 16 байт.

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

DECLARE Long CopyRect IN WIN32API String @ Dst, String Src * Формуємо структуру Src cSrc = BINTOC(nLeft,"4RS") + BINTOC(nTop,"4RS") +; BINTOC(nRight,"4RS") + BINTOC(nBottom,"4RS") * Підготовляємо місце для структури Dst cDst = REPLICATE(CHR(0),16) nResult = CopyRect(@cDst, cSrc) && Копіювання

У наведеному прикладі показано, як, використовуючи функцію CTOBIN, можна "розібрати" структуру, отримавши числові значення її полів:

NLeft = CTOBIN(SUBSTR(cDst,1,4), "4RS") && RECT.left nTtop = CTOBIN(SUBSTR(cDst,5,4), "4RS") && RECT.top nRight = CTOBIN(SUBSTR(cDst, 9,4), "4RS") && RECT.right nBottom = CTOBIN(SUBSTR(cDst,13,4), "4RS") && RECT.bottom

Структури, що містять вказівники

Досить часто зустрічається ситуація, коли передається Windows API функції структура містить покажчики. Як приклад розглянемо функцію StartDoc, що створює документ друку на принтері. Ось її прототип:

Int StartDoc(HDC hdc, // handle to DC CONST DOCINFO* lpdi// contains file names);

Як бачите, другий параметр, що передається, - це покажчик на структуру DOCINFO. Ось ця структура:

Typedef struct ( int cbSize; LPCTSTR lpszDocName; LPCTSTR lpszOutput; LPCTSTR lpszDatatype; DWORD fwType; ) DOCINFO, * LPDOCINFO;

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

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

Існує кілька можливостей отримати блок пам'яті. Можна взяти " шматочок " як із загальної пам'яті Windows, і з пам'яті, виділеної процесу (тобто вашому додатку). Другий спосіб має більш високу швидкодію, проте тут ми розглянемо спосіб роботи з пам'яттю Windows як простіший.

Функція GlobalAlloc отримує у Windows блок пам'яті вказаного розміру та повертає покажчик на нього. Ось прототип цієї функції:

HGLOBAL GlobalAlloc(UINT uFlags, // атрибути розподілу пам'яті SIZE_T dwBytes// Розмір у байтах);

Параметр uFlagsвизначає, як розподілятиметься пам'ять. У MSDN написано, що може приймати одне з наступних значень:

З таблиці слід, що з параметра uFlagsслід використовувати значення GPTR. Але як дізнатися, якеце значення? Знайдіть у MSDN опис функції GlobalAlloc та у розділі Requirementsподивіться, у якому include-файлі знаходиться її прототип. Це є файл Winbase.h. Саме в ньому слід шукати опис значень констант. Ось фрагмент цього файлу, у якому визначаються перелічені у таблиці константи:

/* Global Memory Flags */ #define GMEM_FIXED 0x0000 #define GMEM_MOVEABLE 0x0002 #define GMEM_NOCOMPACT 0x0010 #define GMEM_NODISCARD 0x0020 #define GMEM_ZEROINIT 0x0040 #define GMEM_MODIFY 0x0080 #define GMEM_DISCARDABLE 0x0100 #define GMEM_NOT_BANKED 0x1000 #define GMEM_SHARE 0x2000 #define GMEM_DDESHARE 0x2000 #define GMEM_NOTIFY 0x4000 #define GMEM_LOWER GMEM_NOT_BANKED #define GMEM_VALID_FLAGS 0x7F72 #define GMEM_INVALID_HANDLE 0x8000 #define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_ZEROINIT

Отже, GPTR = GMEM_FIXED + GMEM_ZEROINIT = 0x0000 + 0x0040 = 0x0040.

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

uFlags, Long dwBytes cDocumentName = "Ім'я документа, що друкується" && Ім'я документа nLenDocumentName = LEN(cDocumentName) && Довжина рядка hGlobal = GlobalAlloc(GPTR, nLenDocumentName)

Наступне завдання – переписати вміст змінної cDocumentName в отриманий блок пам'яті. Для цього скористаємося вбудованою функцією Visual FoxPro SYS(2600). Ось її синтаксис:

SYS(2600, dwAddress, nLenght [, cNewString])

Функція поводиться по-різному в залежності від того, вказаний параметр cNewStringчи ні.

Якщо параметр cNewString вказано, то функція копіює nLenghtбайт із змінної cNewStringна згадку за адресою, вказаною в dwAddress.

Якщо параметр cNewString не вказано, то функція повертає nLenghtбайт з пам'яті за адресою, вказаною в dwAddress.

Як бачите, параметр dwAddress- це покажчик.

Запишемо вміст рядка cDocumentName у виділену функцією GlobalAlloc пам'ять:

SYS(2600, hGlobal, nLenDocumentName, cDocumentName)

Ось повний код формування структури DOCINFO:

#DEFINE GPTR 0x0040 DECLARE Long GlobalAlloc IN WIN32API Long uFlags, Long dwBytes cDocumentName = "Ім'я документа, що друкується" nLenDocumentName = LEN(cDocumentName) hGlobal = GlobalAlloc(GPTR, nLenDocumentName) SYS(2600, dwAddress, nLenght [, cNewString]) * Починаємо формувати структуру DOCINFO * cDocInfo – змінна, в якій формується структура cDocInfo = BINTOC(20,"4RS") && DOCINFO. cbSize cDocInfo = cDocInfo + BINTOC(hGlobal,"4RS") && DOCINFO. lpszDocName cDocInfo = cDocInfo + REPLICATE(CHR(0),12) && Інші поля структури * Кінець формування структури

Структура формується у змінній cDocInfo. У перші чотири байти записуємо число 20 - це розмір структури (поле cbSize). Наступні чотири байти, що додаються до змінної, - це покажчик на область пам'яті, в яку переписано вміст змінної cDocumentName - найменування документа. Потім до змінної додаються ще дванадцять нульових байтів – це поля структури lpszOutput, lpszDatatypeі fwType, які, згідно з документацією, можуть ігноруватися; це означає, що поля повинні мати нульові значення. Таким чином, вийшов рядок довжиною 20 байтів – що й вимагалося.

Особливості використання пам'яті

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

Ось прототип функції GlobalFree:

HGLOBAL GlobalFree (HGLOBAL hMem // покажчик блок пам'яті);

Функція отримує лише один параметр – покажчик на блок пам'яті. Ось її оголошення та використання у Visual FoxPro:

DECLARE Long GlobalFree IN WIN32API Long hGlobal GlobalFree(hGlobal)

Тут hGlobal - покажчик блок пам'яті, повертається функцією GlobalAlloc.

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

Передача у функцію масивів

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

Масив Visual FoxPro має зовсім іншу організацію, яка дозволяє зберігати в його елементах різні типи даних. Тому неможливо передати масив з Visual FoxPro в функцію Windows API, просто вказавши його ім'я як параметр. Більше того, команда DECLARE..DLL не має такого типу даних, як масиви.

Проте вихід із цього становища є. Як і структур, передачі масивів використовуються символьні змінні. Числові дані з масиву повинні бути перетворені в рядок, який ви і передаєте як параметр у Windows API функцію. У цьому прикладі показаний код функції ArrayToString, що формує рядок з масиву. Функція отримує отримує масив taArray(за посиланням) та прапор tlTypeOfValue, Який вказує, як потрібно перетворити значення елементів масиву - як цілі ( tlTypeOfValue=.F.) або речові ( tlTypeOfValue=.T.) числа:

FUNCTION ArrayToString PARAMETERS taArray, tlTypeOfValue EXTERNAL ARRAY taArray LOCAL lnLenArray, lnIndex, lcStruct, lcType lcType = IIF(tlTypeOfValue = .t., "F", "4RS") lnLenArray = AL& FOR lnIndex = 1 TO lnLenArray lcStruct = lcStruct + BINTOC(taArray, lcType) ENDFOR RETURN lcStruct ENDFUNC

Функція працює як з одновимірними, так і двовимірними масивами.

Кодування символьних даних: формати ANSI та UNICODE

У кодуванні ANSI (застосовуваної Visual FoxPro) кожен символ визначається одним байтом, тобто максимальна кількість символів дорівнює 256, що, звичайно, дуже мало. Наприклад, при русифікації частина стандартних символів ANSI замінюється на символи кирилиці. А в деяких алфавітах, наприклад, японській кані, стільки символів, що одного байта для їхнього кодування просто недостатньо. Для підтримки таких мов, і, що важливіше, для полегшення "перекладу" програм іншими мовами, було розроблено кодування Unicode. Кожен символ Unicode складається з двох байтів, що дозволило розширити набір допустимих символів до 65536.

Досить багато функцій Windows API використовують під час роботи з символьними рядками формат Unicode. Для роботи з такими функціями необхідно конвертувати символьні дані з одного формату в інший.

Вбудована функція Visual FoxPro STRCONV виконує конвертування рядків з формату ANSI в UNICODE, так і назад. Ось її синтаксис:

STRCONV( cExpression, nConversionSetting [, nRegionalIdentifier [, nRegionalIDType]])

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

  • 5 - конвертування з ANSI в UNICODE
  • 6 - конвертування з UNICODE в ANSI

Необов'язкові параметри nRegionalIdentifierі nRegionalIDTypeвизначають додаткові регіональні налаштування та можуть бути безболісно проігноровані.

Нижче наведено приклади використання функції STRCONV:

CUnicodeString = STRCONV(cANSIString, 5) && Конвертування в Unicode cANSIString = STRCONV(cUnicodeString, 6) && Конвертування в ANSI

Можливо, при прочитанні цього розділу у вас склалося враження, що працювати з Windows API функціями Visual FoxPro досить просто. І так і ні. Наприклад, деякі функції API використовують типи даних, що не підтримуються в команді DECLARE..DLL, наприклад, 64-розрядні цілі числа. Також є ряд функцій, які називаються функціями зворотного виклику. У прототипі такої функції перед описом типу, що повертається, присутній модифікатор CALLBACK. На жаль, ви не можете використовувати такі функції принаймні безпосередньо. Також буває, що структура містить покажчик на функцію - такі API у Visual FoxPro так само не можна використовувати.

Disclaimer

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

Але не все так просто, як здається.

Чому зараз про WinAPI?

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

Про що я? А ось шматочку коду:

Case WM_KEYDOWN: MessageBox(hwndDlg,"Die!","I"m dead!",MB_YESNO|MB_ICONINFORMATION);
Таким чином, автори хотіли додати підтримку клавіатури, але сувора реальність надр архітектури діалогових вікон у Windows жорстко припинила таку самодіяльність. Ті, хто користувався емулятором і відладником у ньому, хоч раз бачили це повідомлення?
У чому проблема?

Відповідь така: так робити не можна!

І, повертаючись, до первісного питання WinAPI: дуже багато популярних, і не дуже, проектів продовжують його використовувати і в даний час, т.к. краще, ніж на чистому API багато речей не зробити (тут можна нескінченно наводити аналогії на кшталт порівняння високорівневих мов та асемблера, але зараз не про це). Та й мало чому? Просто використовують і все.

Про проблему

Діалогові вікна полегшують роботу з GUI, одночасно позбавляючи нас можливості зробити щось самостійно. Наприклад, повідомлення WM_KEYDOWN/WM_KEYUP, що надходять у віконну процедуру, «з'їдаються» у надрах DefDlgProc, беручи він такі речі, як: Навігація по Tab, обробка клавіш Esc, Enter, тощо. Крім того, діалоги не потрібно створювати вручну: простіше, адже накидати кнопок, списків, у редакторі ресурсів, викликати у WinMain CreateDialog/DialogBox і все готове.

Обійти такі дрібні проблеми просто. Є, як мінімум, два цілком легальні способи:

  1. Створити свій власний клас через RegisterClassEx та в процедурі обробки класу схоплювати WM_KEYDOWN, перенаправляти у процедуру обробки самого діалогу. Так Так! Можна створювати діалоги зі своїм власним класом, і вбудований VS редактор навіть дозволяє задавати ім'я класу для діалогу. Ось тільки хтось про це знає і цим користується?
    Мінус очевидний: Потрібно реєструвати ще один клас, мати на 1 CALLBACK процедуру більше, суть якої буде лише в трансляції пари повідомлень. Крім того, ми не знатимемо кудиїх транслювати, і доведеться городити милиці.
  2. Використовувати вбудований механізм акселераторів. І нам навіть не доведеться змінювати код діалогової процедури! Ну, хіба що, додати один рядок у switch / case, але про це нижче.

Tutorials?

Не побоюсь сказати, що УсеТуторіали зі створення вікон через WinAPI починаються з такого нехитрого коду, позначаючи його, як «цикл обробки повідомлень» (опущу деталі з підготовки класу вікна та іншу обв'язку):

While (GetMessage(&msg, nullptr, 0, 0)) ( TranslateMessage(&msg); DispatchMessage(&msg); )
Тут справді все просто:

  1. GetMessage() вихоплює чергове повідомлення з черги, та ключовий момент: блокує потік, якщо черга порожня.
  2. TranslateMessage() з WM_KEYDOWN/WM_KEYUP формує повідомлення WM_CHAR/WM_SYSCHAR (вони потрібні, якщо хтось хоче зробити свій редактор тексту).
  3. DispatchMessage() відправляє повідомлення у віконну процедуру (якщо існує).
Почнемо з того, що цей код використовувати небезпечно, і ось чому. Зверніть увагу на виноску:
Тому що return value can be nonzero, zero, or -1, avoid code like this:
while (GetMessage(lpMsg, hWnd, 0, 0)) ...
І нижче наводиться приклад правильного циклу.

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

Після цього фрагмента коду, як правило, слідує розповідь про акселератори, і додається пара нових рядків (з огляду на зауваження в MSDN, пропоную відразу писати правильний цикл):

HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!TranslateAccelerator(msg.hwnd, hAccel, &msg)) ( TranslateMessage(&msg); DispatchMes ; ) )
Цей варіант я найчастіше бачив. І він ( та-дам) Знову неправильний!

Спочатку про те, що змінилося (потім про проблеми цього коду):

У першому рядку з ресурсів завантажується таблиця клавіш, при натисканні на які формуватиметься повідомлення WM_COMMAND з відповідним id команди.

Власне TranslateAccelerator цим і займається: якщо бачить WM_KEYDOWN та код клавіші, які є в цьому списку, то (знову ж таки ключовий момент) буде формувати повідомлення WM_COMMAND (MAKEWPARAM(id, 1)) і відправляти у відповідне для дескриптора вікна, зазначеного в першому аргументі , процедури обробки.

З останньої фрази, гадаю, стало зрозуміло, у чому проблема попереднього коду.
Поясню: GetMessage вихоплює повідомлення для ВСІХ об'єктів типу «вікно» (до яких входять і дочірні: кнопки, списки та інше), а TranslateAccelerator буде відправляти сформовану WM_COMMAND куди? Правильно: назад у кнопку/список тощо. Але ми обробляємо WM_COMMAND у своїй процедурі, а значить, нам цікаво її отримувати в ній же.

Ясно, що TranslateAccelerator треба викликати для нашого створеного вікна:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) ( TranslateMessage(&msg); DispatchMess;) )
І наче все добре і чудово тепер: ми розібрали все детально і все має працювати ідеально.

І знову ні. :-) Це буде працювати правильно, поки у нас рівно одне вікно - наше. Як тільки з'явиться немодальне нове вікно (діалог), всі клавіші, які будуть натиснуті в ньому відтранслюються в WM_COMMAND і відправлятися куди? І знову ж таки правильно: у наше головне вікно.

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

IsDialogMessage

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

Насправді робить вона трохи більше, ніж випливає з назви. А саме:

  • Здійснює навігацію за дочірніми контролами кнопками Tab/Shift+Tab/вгору/вниз/вправо/ліворуч. Плюс ще щось, але цього нам достатньо
  • Після натискання на ESC формує WM_COMMAND(IDCANCEL)
  • Після натискання на Enter формує WM_COMMAND(IDOK) або натискання на поточну кнопку за замовчуванням
  • Перемикає кнопки за замовчуванням (рамка у таких кнопок трохи яскравіша за інші)
  • Ну і ще різні штуки, що полегшують користувачеві роботу з діалогом
Що вона нам дає? По-перше, нам не треба думати про навігацію усередині вікна. Нам і так усе зроблять. До речі, навігацію по Tab можна зробити, додавши стиль WS_EX_CONTROLPARENT нашому основному вікну, але це незграбно і не так функціонально.

По-друге, вона нам полегшить життя по всіх інших пунктах, перелічених у списку (і навіть трохи більше).

Взагалі, вона використовується десь у надрах Windows для забезпечення роботи модальних діалогових вікон, А програмістам дано, щоб викликати її для немодальних діалогів. Однак ми її можемо використовувати де завгодно:

Більше того,щоДіалог Message функція міститься в моделі діаграми коробки, ви можете використовувати з будь-яким окном, що знаки керують, щоб налаштувати windows, щоб зробити саму клавіатуру, як ви використовуєте в dialog box.
Тобто. тепер, якщо ми оформимо цикл так:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) ( if (!IsDialogMessage(hMainW) TranslateMessage(&msg); DispatchMessage(&msg); ) ) )
То наше вікно матиме навігацію, як у рідному діалозі Windows. Але тепер ми отримали два недоліки:

  1. Цей код також буде добре працювати тільки з одним (немодальним) вікном;
  2. Отримавши всі переваги діалогової навігації, ми втрачаємо принади у вигляді повідомлень WM_KEYDOWN/WM_KEYUP (тільки для самого вікна, а не для дочірніх контролів);
І ось на цьому етапі взагалі всі туторіали закінчуються і починаються питання: How to handle keyboard events in a winapi standard dialog?
Це перше посилання в кути, але повірте: тисячі їх. Про запропоновані рішення (краще з яких - це створити свій клас діалогів, про що я писав вище, до subclassing і RegisterHotKey. Десь я навіть бачив "кращий" спосіб: використовувати Windows Hooks).

Настав час поговорити про те, чого немає в туторіалах і відповідях.

Як правило (як правило! Якщо комусь захочеться більшого, то можна реєструвати свій клас для діалогів і працювати так. І якщо ж, комусь це цікаво, я можу доповнити цим статтю) WM_KEYDOWN хочуть тоді, коли хочуть обробити натискання на клавішу, яка виконає функцію незалежно від обраного контролю у вікні - тобто. якась загальна функція для цього конкретного діалогу. А якщо так, то чому б не скористатися багатими можливостями, які нам сама WinAPI і пропонує: TranslateAccelerator.

Скрізь використовують рівно однутаблицю акселераторів, і лише головного вікна. Ну справді: цикл GetMessage-loop один, отже, і таблиця одна. Куди ще їх подіти?

Насправді цикли GetMessage-loop можуть бути вкладеними. Давайте ще раз подивимося опис PostQuitMessage:

Функції PostQuitMessage posts a WM_QUIT message to the thread"s message queue and returns immediately;
І GetMessage:
Якщо функція відновить WM_QUIT message, return value is zero.
Таким чином, вихід із GetMessage-loop здійсниться, якщо ми викличемо PostQuitMessage у процедурі вікна. Що це означає?

Ми можемо для кожного немодального вікна у нашій програмі створювати свій власний подібний цикл. У разі DialogBoxParam нам підходить, т.к. воно крутить свій цикл і вплинути ми не можемо. Однак якщо створимо діалог через CreateDialogBoxParam або вікно через CreateWindow, можна закрутити ще один цикл. При цьому в кожномутакому вікні та діалозі ми повинні викликати PostQuitMessage:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) ( if (!IsDialogMessage(hMainWnd) TranslateMessage(&msg); hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc), HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG)); as dialog window is modal while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!TranslateAccelerator(hDlg, hAccel, &msg)) ( if (!isDia , &msg)) ( TranslateMessage(&msg); DispatchMessage(&msg); ) ) ) EnableWindow(hwnd, fSavedEnabledState); // enable parent window. Dialog was closed break; ) ) INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) ( switch(umsg) ( case WM_CLOSE: ( // EndDialog(hwnd, 0)); - DONT DO THAT! // EndDialog valid ONLY for Modal Dialogs, створені з DialogBox(Param) DestroyWindow(hwnd); break; ) case WM_DESTROY: ( PostQuitMessage(0); break; ) // .... ) return 0; )
Зверніть увагу: тепер для кожного нового вікна у нашій програмі ми можемо додати до обробки власнутаблицю акселераторів. WM_QUIT вихоплюватиме GetMessage із циклу для діалогу, а зовнішній цикл його навіть не побачить. Чому так відбувається?

Справа в тому, що зовнішній цикл «встав» на виклик DispatchMessage, який викликав нашу процедуру, яка крутить свій власний внутрішнійцикл GetMessage з таким же DispatchMessage. Класичний вкладений дзвінок (в даному випадку DispatchMessage). Тому зовнішній цикл не отримає WM_QUIT і завершиться цьому етапі. Все працюватиме струнко.

Але і тут є свої недоліки:
Кожен такий цикл оброблятиме повідомлення тільки для «свого» вікна. Про інші ми тут не знаємо. А значить, якщо десь з'явиться ще один цикл, то решта всіх вікон не отримуватиме потрібної обробки своїх повідомлень парою TranslateAccelerator/IsDialogMessage.

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

Робимо красиво

Т.к. правильна постановка задачі є половиною розв'язання, то спершу треба це завдання правильно ж і поставити.

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

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

Створимо простий std::map, який буде мапит дескриптор вікна в дескриптор таблиці акселераторів. Ось так:

Std::map l_mAccelTable;
І в міру створення вікон будемо до нього додавати нові вікна з дескриптором на свою улюблену таблицю (або нуль, якщо така обробка не потрібна).

BOOL AddAccelerators(HWND hWnd, HACCEL hAccel) ( if (IsWindow(hWnd)) ( l_mAccelTable[ hWnd ] = hAccel; return TRUE; ) return FALSE; ) hInstance, accel)); ) BOOL AddAccelerators(HWND hWnd, int accel) ( return AddAccelerators(hWnd, MAKEINTRESOURCE(accel));
Та й після закриття вікна видаляти. Ось так:

Void DelAccel(HWND hWnd) ( std::map ::iterator me = l_mAccelTable.find(hWnd); if (me != l_mAccelTable.end()) ( if (me->second) ( DestroyAcceleratorTable(me->second); ) l_mAccelTable.erase(me); ) )
Тепер, як створюємо новий діалог/вікно, викликаємо AddAccelerators(hNewDialog, IDR_MY_ACCEL_TABLE). Як закриваємо: DelAccel(hNewDialog).

Список вікон із потрібними дескрипторами у нас є. Дещо модифікуємо наш основний цикл обробки повідомлень:

// ... HWND hMainWnd = CreateWindow(...); AddAccelerators(hMainWnd, IDR_ACCELERATOR); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) ( if (-1 == bRet) break; if (!HandleAccelArray(GetActiveWindow(), msg)) ( TranslateMessage(&msg); DispatchMessage ) ) // ...
Значно краще! Що ж там у HandleAccelArray і навіщо там GetActiveWindow()?

Трохи теорії:

Є дві функції, що повертають дескриптор активного вікна GetForegroundWindow та GetActiveWindow . Відмінність першої від другої цілком зрозуміло описано в описі другої:

Return value is the handle to the active window attached to the calling thread"s message queue. Otherwise, the return value is NULL.
Якщо перша повертатиме дескриптор будь-якого вікна в системі, то остання тільки того, яке використовує чергу повідомлень нашого потоку. Т.к. нас цікавлять вікна тільки нашого потоку (а значить ті, які потраплятимуть до нашої черги повідомлень), то й візьмемо останню.

Так ось HandleAccelArray, керуючись переданим їй дескриптором на активне вікно, шукає це саме вікно в нашій карті, і якщо воно там є, віддає це повідомлення на трансляцію TranslateAccelerator, а потім (якщо перший не побачив потрібного) в IsDialogMessage. Якщо й остання не обробила повідомлення, повертаємо FALSE, щоб пройти за стандартною процедурою TranslateMessage/DispatchMessage.

Виглядає так:

BOOL HandleAccelWindow(std::map ::const_iterator mh, MSG & msg) ( const HWND & hWnd = mh->first; const HACCEL & hAccel = mh->second; if (!TranslateAccelerator(hWnd, hAccel, &msg)) ( // message not for Translate Try it with IsDialogMessage if (!IsDialogMessage(hWnd, &msg)) ( // so, do default stuff return FALSE; ) ) // ok, message translated. (HWND hActive, MSG & msg) ( if (!hActive) return FALSE; // no active window. Nothing to do std::map ::const_iterator mh = l_mAccelTable.find(hActive); if (mh != l_mAccelTable.end()) ( // Got it! Try to translate this message for the active window return HandleAccelWindow(mh, msg); ) return FALSE; )
Тепер кожне дочірнє вікно має право додати собі улюблену таблицю акселераторів і спокійно ловити та обробляти WM_COMMAND з потрібним кодом.

А що там ще про один рядок у коді обробника WM_COMMAND?

Опис в TranslateAccelerator говорить:
Щоб відрізняти message, що це функція sends з messages sent by menus або controls, high-order word of wParam parametr WM_COMMAND or WM_SYSCOMMAND message contains the value 1.
Зазвичай код обробки WM_COMMAND виглядає так:

Switch(HIWORD(wParam)) ( case BN_CLICKED // Command from buttons/menus ( switch(LOWORD(wParam))) ( case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff ) break; ) )
Тепер можна написати так:

Switch(HIWORD(wParam)) ( case 1: // accelerator case BN_CLICKED: // Command from buttons/menus ( switch(LOWORD(wParam)) ( case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUT) ; // ... ) break; ) )
І тепер, повертаючись до того ж fceux, додавши всього один рядокв код обробки команд від кнопок ми отримаємо бажане: керувати дебагером з клавіатури. Достатньо додати невелику обгортку навколо головного циклу повідомлень та нову таблицю акселераторів з потрібними відповідностями VK_KEY => IDC_DEBUGGER_BUTTON.

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

P.P.S.: Т.к. DialogBox/DialogBoxParam крутить власний цикл, то при виклику діалогу через них акселератори не працюватимуть і наш цикл (або цикли) буде «простоювати».

P.P.P.S.: Після виклику HandleAccelWindow карт l_mAccelTable може змінитися, т.к. TranslateAccelerator або IsDialogMessage викликають DispatchMessage, а там може зустрітися AddAccelerators або DelAccel у наших обробниках! Тому краще його після цієї функції не чіпати.

Помацати код можна. За основу було взято код, що генерується із стандартного шаблону MS VS 2017.

Теги: Додати теги

Абревіатура API, Application Programming Interface (API) – це просто деякий готовий набір функцій, який можуть використовувати розробники програм. Загалом це поняття еквівалентне тому, що раніше частіше називали бібліотекою підпрограм. Однак найчастіше під API мається на увазі деяка особлива категорія таких бібліотек.

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

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

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

Відмінність полягає в тому, що склад функцій Windows API, з одного боку, значно ширший, порівняно з DOS, з іншого - не включає багато засобів прямого управління ресурсами комп'ютера, які були доступні програмістам у попередній ОС. Крім того, звернення до Windows API виконується за допомогою звичайних процедурних звернень, а виклик функцій DOS через спеціальну машинну команду процесора, яка називається Interrupt («переривання»).

Win16 API та Win32 API

Як відомо, зміна Windows 3.x на Windows 95 ознаменувала собою перехід від 16-розрядної архітектури операційної системи до 32-розрядної. Одночасно відбулася заміна 16-розрядного Windows API (Win16 API) на новий 32-розрядний варіант (Win32 API). В даному випадку потрібно просто мати на увазі, що за невеликим винятком набір Win32 API є єдиним для сімейств Windows 9x та Windows NT.

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

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

Вбудовані функції реалізують лише окремий випадок відповідної API-функції. Це досить простий варіант.

Величезна кількість API-функцій взагалі не мають аналогів у існуючому сьогодні варіанті компіляторів. Наприклад, видалити каталог не можна засобами VB – для цього потрібно використовувати функцію DeleteDirectory.

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

Win APIіDynamic Link Library (DLL)

Набір Win API реалізований як динамічних DLL-бібліотек.

У цьому випадку під DLL ми маємо на увазі традиційний варіант двійкових динамічних бібліотек, які забезпечують пряме звернення до потрібних процедур - підпрограм або функцій (приблизно також як це відбувається при виклику процедур всередині проекту). Такі бібліотеки можна створювати з допомогою різних інструментів - VC++, Delphi, Fortran, Assembler.

Зазвичай файли динамічних бібліотек мають розширення .DLL, але це не обов'язково. Для Win16 часто застосовувалося розширення. EXE, драйвери зовнішніх пристроїв позначаються за допомогою .DRV.

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

Бібліотеки Win32 API ядра операційної системи Windows 95/98:

KERNEL32.DLL: низькорівневі функції керування пам'яттю, завданнями та іншими ресурсами системи;

USER32.DLL: тут в основному знаходяться функції керування інтерфейсом користувача;

GDI32.DLL: бібліотека Graphics Device Interface – різноманітні функції виведення на зовнішні пристрої;

COMDLG32.DLL: функції, пов'язані з використанням діалогових вікон загального призначення.

Основні бібліотеки з функціями розширення:

COMCTL32.DLL: набір додаткових елементів керування Windows, у тому числі Tree List та Rich Text;

MAPI32.DLL: функції роботи з електронною поштою;

NETAPI32.DLL: елементи керування та функції роботи з мережею;

ODBC32.DLL: функції бібліотеки потрібні для роботи з різними базами даних через протокол ODBC;

WINMM.DLL: операції доступу до системних засобів мультимедіа.

Ця стаття адресована таким же, як і я новачкам у програмуванні на С++, які з волі випадку або за бажанням вирішили вивчати WinAPI.
Хочу одразу попередити:
Я не претендую на звання гуру C++ або WinAPI.
Я тільки вчуся і хочу навести тут кілька прикладів та порад, які полегшують мені вивчення функцій та механізмів WinAPI.

У цій статті я припускаю, що ви вже досить ознайомилися з С++, щоб уміти створювати класи і перевантажувати для них різні оператори і що ви вже «ховали» якісь свої механізми в клас.

Створення та використання консолі

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

If (AllocConsole())
{



std::ios::sync_with_stdio();
}
Для зручності раджу обернути його на функцію. Наприклад:
void CreateConsole()
{
if (AllocConsole())
{
int hCrt = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), 4);
*stdout = *(::_fdopen(hCrt, "w"));
::setvbuf(stdout, NULL, _IONBF, 0);
*stderr = *(::_fdopen(hCrt, "w"));
::setvbuf(stderr, NULL, _IONBF, 0);
std::ios::sync_with_stdio();
}

Викликана консоль працює тільки в режимі виводу і працює також як і в консольних додатках. Виводьте інформацію як і зазвичай – cout/wcout.
Для працездатності даного коду необхідно включити до проекту наступні файли:
#include
#include #include
і включити простір імен std у глобальний простір імен:
using namespace std;
Звичайно ж, якщо ви не хочете цього робити, то просто допишіть std:: до всіх сутностей, які в ній знаходяться.

Спадкування об'єктів для виведення та арифм. операцій

При створенні та вивченні самих «віконець» мені завжди потрібно виводити в консоль якесь значення.
Наприклад:
Ви отримуєте розмір клієнтської області вікна за допомогою функції GetClientRect куди як параметр передається адресу об'єкта структури RECT, щоб заповнити цей об'єкт даними. Якщо вам потрібно дізнатися розмір отриманої клієнтської області, ви просто можете вивести його в вже підключену консоль.

Cout<

Але робити так щоразу (особливо якщо вам часто доводиться робити щось подібне) дуже незручно.
Тут нам на допомогу приходить успадкування.
Створіть клас, який відкрито успадковується від структури RECT і перевантажте оператор виводу<< так, как вам угодно.
Наприклад:

Class newrect:public RECT
{
public:
friend ostream& operator<<(ostream &strm,newrect &rect)
{
strm<<"Prtint RECT object:\n";
strm<return strm;
}
};

Тепер просто виводьте об'єкт за допомогою cout/wcout:

Cout<

І вам у зручному вигляді виводитиметься все так, як вам потрібно.
Також ви можете зробити з будь-якими потрібними вам операторами.
Наприклад, якщо треба порівнювати або привласнювати структури (припустимо той самий RECT або POINT) - перевантажте operator==() та operator=() відповідно.
Якщо хочете реалізувати оператор менше< что бы быстро сравнивать размеры окна и т.д. перегрузите operator<().
Так ви можете робити, я припускаю, майже з будь-якими структурами і найголовніше, що всі функції, які працюють зі звичайним об'єктом структури RECT, так само добре будуть працювати і з його спадкоємцем.
І ще рекомендую всю цю красу винести в окремий файл, що підключається, і використовувати при необхідності.

Свій клас

Не знаю як у інших, але так як я зовсім зелений, я вирішив для кожної вивченої функції або для кожної глави / під глави книги створювати новий проект, що все було по поличках і можна було в будь-який момент повернутися і освіжити в пам'яті необхідні моменти .
Так як у WinAPI навіть для створення найпростішого вікна потрібно заповнити структуру класу, зареєструвати її і написати тривіальну віконну процедуру, я після третього або четвертого проекту згадав, що я все-таки на С++ пишу.
У результаті я все сховав у простенький клас. Хендл вікна, його ім'я, ім'я класу, адреса віконної процедури, клас вікна (WNDCLASS) все заховано в приватній секції класу.
Для їх отримання досить описати прості методи-Get" ери, наприклад:
HWND GetHWND()
LPCTSTR GetClsName() і т.д.
Заповнення та реєстрація віконного класу, створення самого вікна та його показ проводитися у конструкторі.
Для зручності можна перевантажити конструктор, а заповнення та реєстрацію віконного класу сховати в окрему private функцію класу та викликати у кожному з конструкторів. Зручність перевантаження полягає в тому, що мені іноді необхідно створити зовсім просте вікно і я викликаю конструктор з двома параметрами - ім'я вікна і hinstance програми.
Іншим разом мені потрібно створити вікно з особливими розмірами, не з дефолтною процедурою вікна і з якимось іншим певним стилем – я викликаю конструктор із супутніми параметрами.
Цей клас у мене визначений в файлі, що окремо підключається, який лежить в include папці IDE.
Шаблон такого класу:
class BaseWindow
{
WNDCLASSEX _wcex;
TCHAR _className;
TCHAR _windowName;
HWND _hwnd;
bool _WindowCreation();
public:
BaseWindow(LPCTSTR windowName,HINSTANCE hInstance,DWORD style,UINT x,UINT y,UINT height,UINT width);
BaseWindow(LPCTSTR windowName,HINSTANCE hInstance);
const HWND GetHWND()const(return HWND;)
LPCTSTR GetWndName()const(return _windowName;)
};

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

P.S.

Все описане справедливо для:
Платформа – Windows 7 32 bit
IDE - Visual Studio 2010
Може у когось ці поради викликатимуть сміх та іронію, але все-таки ми всі колись у чомусь були новачками/стажерами/junior”ами.
Прошу до посту поставитися з розумінням. Конструктивна критика, звичайно ж, вітається. Жорсткі диски