Базові сервіси windows та функції api. Windows api - набір функцій операційної системи. Чому про WinAPI зараз

Нарешті! Нарешті! Сьогодні ми почнемо створювати повноцінне вікно 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; ;

Структура 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.

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 може бути 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 для забезпечення роботи модальних діалогових вікон, а програмістам дана, щоб викликати її для немодальних діалогів. Однак ми її можемо використовувати де завгодно:

Більше того,щодіалоговафункція функції впроваджується для моделювання діалогових boxів, ви можете використовувати його з будь-яким окном, що знаки керування, налаштовуючи 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(hMainWnd) 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); DispatchMessage(&msg); ) ))) // .... hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc), HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG)); wnd, FALSE);// disable parent window, 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. wparam, LPARAM lparam) (switch(umsg) (case WM_CLOSE: (// EndDialog(hwnd, 0)); - DONT DO THAT! // EndDialog є правильним тільки для модифікованих діалогів, створених з 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; ) BOOL AddAccelerators(HWNDhW LoadAccelerators( hInstance, accel));) BOOL AddAccelerators(HWND hWnd, int 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 . Відмінність першої від другої цілком зрозуміло описано в описі другої:

Зворотне значення 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 говорить:
Для того, щоб відрізняти повідомлення, що ця функція sends з messages означає menu або controls, високоефективне слово з wParam parametr WM_COMMAND або 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.

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

C WinAPI - це основний набір програмних інтерфейсів (API) Microsoft, доступних в операційних системах Рання версія називалася Win32 API.

Вступ

C WinAPI — це прикладний програмний інтерфейс, який використовується для створення програм Windows. Для початку роботи користувач повинен завантажити SDK Windows, раніше відомий як Platform SDK.

Містить файли заголовків, бібліотеки, зразки, документацію та інструменти, які використовуються для розробки програм. API для мов програмування C та C++. Це найпряміший спосіб створення додатків в операційній системі від компанії.

C WinAPI можна розділити на кілька областей:

    базові послуги;

    безпека;

  • користувальницький інтерфейс;

    мультимедіа;

    оболонка Windows;

    мережеві служби.

Базові служби забезпечують доступ до основних ресурсів. До них відносяться функції C WinAPI, файлові системи, пристрої, процеси, потоки, реєстр та обробка помилок. Область безпеки надає інтерфейси, об'єкти та інші елементи програмування для автентифікації, авторизації, криптографії та інших пов'язаних із безпекою завдань. Підсистема графіки забезпечує функціональність виведення графічного вмісту на монітори, принтери та інші пристрої виведення. Інтерфейс користувача забезпечує функціональність для створення вікон та елементів керування.

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

Компоненти

При створенні WinAPI C слід враховувати базові можливості Windows API, які можна впорядкувати в семи категоріях. Розглянемо кожну їх докладніше.

Основні послуги надають доступ до базових системних ресурсів у Windows. Приклади: файлова система, периферійні пристрої, процеси, доступ до системного реєстру та система керування винятками. Ці функції зберігаються у файлах kernel.exe, krnl286.exe або krnl386.exe для 16-розрядних систем та kernel32.dll та advapi32.dll для 32-розрядних систем.

Графічний інтерфейс забезпечує доступ до ресурсів для відображення на моніторах, принтерах та іншому периферійному обладнанні. Зберігається у файлі gdi.exe на 16-розрядних системах та gdi32.dll у 32-розрядних системах.

Інтерфейс користувача відповідає за перегляд та керування основними елементами, такими як кнопки та смуги прокручування, отримання інформації про клавіатуру та миші, а також пов'язані з ними функції. Ці функції зберігаються у файлі user.exe у 16-розрядних системах та user32.dll comctl32.dll у 32-розрядних системах. Починаючи з версії XP, елементи управління були згруповані в comctl32.dll.

Загальні діалоги — Відображення даних для відкриття та збереження файлів, вибору кольору та шрифту. Знаходяться у файлі comdlg.dll на 16-розрядних системах та comdlg32.dll у 32-розрядних системах.

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

Мережеві служби забезпечують доступ до різних мережних можливостей операційної системи. Його підкомпоненти включають NetBIOS, Winsock, RPC. У старих версіях – NetDDE.

Версії

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

Win32, наступник Win16, був представлений у 1993 році у 32-розрядних продуктах сімейства Windows, таких як Windows NT, 2000, 95. Цей програмний інтерфейс реалізований трьома програмними бібліотеками: Kernel32.dll, User32.dll та GDI32.dll2. Ті ж функції Win32 доступні у всіх продуктах Windows, і, залежно від продукту, використання певних функцій може спричинити помилку обслуговування.

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

Специфікація

C WinAPI це абстрактна специфікація інтерфейсу програмування для операційної системи Windows. Складається з об'яв функцій, об'єднань, структур, типів даних, макросів, констант та інших елементів програмування. WinAPI описується головним і перебуває у заголовках Windows C. Офіційна реалізація функцій WinAPI перебуває у динамічних бібліотеках (DLL): наприклад, kernel32.dll, user32.dll, gdi32.dll чи shell32.dll у системному каталозі. Існують сторонні реалізації Windows API: насамперед проект Wine та проект ReactOS.

Windows API – динамічний об'єкт. Кількість функцій постійно зростає з кожною новою версією ОС та новими пакетами оновлень. Існують також важливі відмінності між версіями сервера та настільними версіями операційної системи. Деякі функції офіційно не документовані.

Pelles C

Pelles C — безкоштовна програма та найкращий компілятор C та інтегроване середовище розробки (IDE) для мови програмування C. Підтримує 32-розрядну Windows (x86) та 64-розрядну Windows (x64). Реалізує як стандарти C99, і C11. Pelles C має вбудований редактор ресурсів, растрове зображення, значок та редактор курсорів та редактор шістнадцяткових дампів. Він розроблений шведським розробником Пелле Орініусом. Назва компілятора має ім'я свого автора. Постачається з SDK, тому програміст одразу може розпочати створення програм без подальшої установки.

Помилка цільової архітектури

Щоб створювати програми Windows API, потрібно увімкнути розширення Microsoft. За замовчуванням вони вимкнені, у зв'язку з чим компілятор видає таке повідомлення про помилку, яке є прикладом C WinAPI з порушеною структурою: fatal error #1014: #error: "No target architecture" («Немає цільової архітектури»). Щоб увімкнути розширення Microsoft, переходимо до параметрів проекту та вибираємо вкладку «Компілятор». На цій вкладці активуємо прапорець "Увімкнути розширення Microsoft".

MSDN

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

Застосування DLL у WinAPI C

Бібліотека загальних елементів керування забезпечує доступ до розширених функцій операційної системи, таких як рядки стану, індикатори виконання, панелі інструментів та вкладки. Ці команди знаходяться в бібліотеці commctrl.dll в 16-розрядних системах і comctl32.dll і згруповані з інтерфейсом користувача.

DLL – це формат файлу динамічної бібліотеки посилань, який використовується для зберігання декількох кодів та процедур для програм Windows. Файли DLL були створені таким чином, що кілька програм могли використовувати їхню інформацію одночасно, допомагаючи зберегти пам'ять. Дозволяє користувачеві редагувати кодування відразу кількох програм без їх зміни. DLL Бібліотеки можна перетворити в статичні, використовуючи MSIL Disassembler або DLL для Lib 3.00.

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

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


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

Windows API – набір функцій операційної системи

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

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

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

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

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

Навіщо потрібний Win API для VB-програмістів

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

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

  1. API-функції, що повністю реалізовані у вигляді вбудованих VB-функцій. Тим не менш, іноді і в цьому випадку буває корисним перейти до застосування API, так як це дозволяє часом істотно підвищити продуктивність (зокрема, за рахунок відсутності непотрібних перетворень параметрів, що передаються).
  2. Вбудовані VB-функції реалізують лише окремий випадок відповідної API-функції. Це досить простий варіант. Наприклад, API-функція CreateDirectory має ширші можливості порівняно з вбудованим VB-оператором MkDir.
  3. Величезна кількість API-функцій взагалі немає аналогів у існуючому сьогодні варіанті мови VB. Наприклад, видалити каталог засобами VB не можна – для цього потрібно використовувати функцію DeleteDirectory.

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

Особиста думка автора така - замість розширення від версії до версії вбудованих функцій VВ слід було б давати хороший опис найбільш ходових API-функцій. У той же час хочеться порадити розробникам не чекати на появу нової версії засобу з розширеними функціями, а уважніше вивчити склад існуючого Win API - цілком імовірно, що потрібні вам можливості можна було реалізувати вже у версії VB 1.0 випуску 1991 року.

Як вивчати Win API

Це не таке просте питання, якщо врахувати, що кількість функцій Win32 API оцінюється в розмірі близько 10 тисяч (точної цифри не знає ніхто, навіть Microsoft).

До складу VB (версій 4-6) входить файл з описом оголошень Win API - WIN32API.TXT (докладніше про його застосування ми розповімо пізніше). Але, по-перше, з його допомогою можна отримати відомості про призначення тієї чи іншої функції та її параметри лише за використовуваними мнемонічним іменами, а по-друге - перелік функцій у цьому файлі далеко не повний. Свого часу (сім років тому) у VB 3.0 були спеціальні довідкові файли з описом функцій Win16 API. Проте вже у v.4.0 ця корисна інформація зі зручним інтерфейсом зникла.

Вичерпну інформацію про Win32 API можна знайти у довідковій системі Platform Software Development Kit, яка, зокрема, знаходиться на компакт-дисках MSDN Library, включених до складу VB 5.0 та 6.0 Enterprise Edition та Office 2000 Developer Edition. Однак розшукати там потрібну інформацію та розібратися в ній зовсім не просто. Не кажучи вже про те, що всі описи там наводяться стосовно мови C.

Загальновизнаним у світі посібником вивчення API-программирования серед VB є книжки відомого американського експерта Даніеля Епплмана (Daniel Appleman). Його серія Dan Appleman's Visual Basic Programmer's Guide до Windows API (для Win16, Win32, стосовно різних версій VB) з 1993 року незмінно входить до бестселерів для VB-програмістів. Книгу Dan Appleman's VB 5.0 Programmer's Guide to the Win32 API, випущену в 1997 році, автору привіз зі США приятель, який знайшов її в першій книгарні невеликого провінційного містечка.

Ця книга об'ємом понад 1500 сторінок включає опис загальної методики API-програмування серед VB, а також більше 900 функцій. Компакт-диск, що додається, містить повний текст книги та всіх програмних прикладів, а крім того, кілька додаткових розділів, які не увійшли до друкованого варіанту. У 1999 році Ден Епплман випустив нову книгу Dan Appleman's Win32 API Puzzle Book and Tutorial для Visual Basic Programmers, яка включає відомості про ще 7600 функцій (хоч і не настільки докладні).

Win API та Dynamic Link Library (DLL)

Набір Win API реалізований як динамічних DLL-бібліотек. Далі фактично піде про технологію використання DLL в середовищі VB на прикладі бібліотек, що входять до складу Win API. Однак, говорячи про DLL, необхідно зробити кілька важливих зауважень.

У цьому випадку під DLL ми маємо на увазі традиційний варіант двійкових динамічних бібліотек, які забезпечують пряме звернення до потрібних процедур - підпрограм або функцій (приблизно так само, як це відбувається при виклику процедур всередині VB-проекту). Такі бібліотеки можуть створюватися за допомогою різних інструментів: VC++, Delphi, Fortran, крім VB (подивимося, що з'явиться у версії 7.0) – останній може робити лише ActiveX DLL, доступ до яких виконується через інтерфейс OLE Automation.

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

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

А тепер кілька порад.

Порада 1. Слідкуйте за правильним оформленням оголошення DL L-процедур

Саме звернення до DLL-процедур у програмі виглядає так само, як до «звичайних» процедур Visual Basic, наприклад:

Call DllName ([список аргументів])

Однак для використання зовнішніх DLL-функцій (у тому числі і Win API) їх потрібно обов'язково оголосити у програмі за допомогою оператора Declare, який має такий вигляд:

Declare Sub Ім'яПроцедури Lib _ “Ім'яБібліотеки” _ [([СписокАргументів])]

Declare Function Ім'яФункції _ Lib “Ім'яБібліотеки” _ [([СписокАргументів])]

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

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

Набір Win32 API реалізований лише у вигляді функцій (у Win16 API було багато підпрограм Sub). Здебільшого це функції типу Long, які найчастіше повертають код завершення операції.

Оператор Declare з'явився в MS Basic ще за DOS, причому він використовувався і для оголошення внутрішніх процедур проекту. Visual Basic цього не потрібно, так як оголошенням внутрішніх процедур автоматично є їх опис Sub або Function. Порівняно з Basic/DOS у новому описі обов'язково вказувати ім'я файлу-бібліотеки, де знаходиться процедура, що шукається. Бібліотеки Wip API розміщуються в системному каталозі Windows, тому достатньо лише назва файлу. Якщо ви звертаєтеся до DLL, яка знаходиться в довільному місці, потрібно записати повний шлях до цього файлу.

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

Declare Function GetTempPath _ Lib “kernel32” Alias ​​“GetTempPathA” _ (ByVal nBufferLength As Long, _ ByVal lpBuffer As String) As Long

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

Порада 2. Будьте особливо уважні під час роботи з DLL-функціями

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

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

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

Проблема ускладнюється ще й тим, що різні мови програмування використовують різні способи передачі параметрів між процедурами. (Точніше, різні способи передачі використовуються за замовчуванням, так як багато мов можуть підтримувати кілька способів.) Win API реалізовані на C/C++ і застосовують угоди про передачу параметрів, прийняті в цій системі, які відрізняються від звичного VB варіанта.

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

Порада 3. Десять рекомендацій Дена Епплмана щодо надійного API-програмування в середовищі VB

Використання функції API вимагає більш уважного програмування з використанням деяких не дуже звичних методів звернення до процедур (проти VB). Далі ми постійно звертатимемося до цих питань. А зараз наведемо виклад сформульованих Деном Епплманом порад на цю тему (їхній перший варіант з'явився ще 1993 року) з деякими нашими доповненнями та коментарями.

1. Пам'ятайте про ByVal.Найчастіша помилка, що здійснюється при зверненні до функцій API і DLL, полягає в некоректному використанні ключового слова ByVal: його або забувають ставити, або, навпаки, ставлять, коли в ньому немає потреби.

На цих прикладах показано вплив оператора ByVal на передачу параметрів

Тип параметра З ByVal Без ByVal
Integer У стек міститься 16-розрядне ціле У стек міститься 32-розрядна адреса 16-розрядного цілого
Long У стек міститься 32-розрядне ціле У стек міститься 32-розрядна адреса 32-розрядного цілого
String Рядок перетворюється на формат, що використовується в С (дані та завершальний нульовий байт). 32-розрядна адреса нового рядка міститься у стек У стек міститься VB-дескриптор рядка. (Такі дескриптори ніколи не використовуються самим Windows API і розпізнаються тільки в DLL, реалізованих спеціально для VB.)

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

Щоб розібратися в цьому, проведіть експеримент за допомогою таких програм:

Dim v As Integer v = 2 Call MyProc(v) MsgBox “v = “ & v Sub MyProc (v As Integer) v = v + 1 End Sub

Запустивши на виконання цей приклад, ви отримаєте повідомлення зі значенням змінної, рівним 3. Справа в тому, що в даному випадку в підпрограму MyProc передається адреса змінної v, фізично створеної програми, що викликає. Тепер змініть опис процедури на

Sub MyProc (ByVal v As Integer)

В результаті при виконанні тесту ви отримаєте v = 2, тому що в процедуру передається лише вихідне значення змінної - результат виконаних з ним операцій не повертається до програми, що викликає. Режим передачі значення можна змінити також за допомогою оператора Call наступним чином:

Sub MyProc (v As Integer) ... Call MyProc ((v)) '(v) - дужки вказують режим передачі _ за значенням.

Однак при зверненні до внутрішніх VB-процедур використання в операторі Call ключового слова ByVal заборонено - замість нього застосовуються круглі дужки. Цьому є пояснення.

У класичному випадку (С, Fortran, Pascal) відмінність режимів ByRef і ByVal залежить від того, що саме міститься в стек обміну даними - адреса змінної або її значення. В Basic історично використовується варіант програмної емуляції ByVal - у стеку завжди знаходиться адреса, але тільки при передачі за значенням для цього створюється тимчасова змінна. Щоб відрізнити ці два варіанти (класичний і Basic), використовуються різні способи опису режиму ByVal. Зазначимо, що емуляція режиму ByVal в VB забезпечує більш високу надійність програми: переплутавши форму звернення, програміст ризикує лише тим, що в програму, що викликає, повернеться (або не повернеться) виправлене значення змінної. У «класичному» варіанті така плутанина може призвести до фатальної помилки під час виконання процедури (наприклад, коли замість адреси пам'яті використовуватиметься значення змінної, рівне, скажімо, нулю).

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

Наслідки неправильної передачі параметрів легко передбачити. У разі отримання явно неприпустимої адреси, вам буде видано повідомлення GPF (General Protection Fault - помилка захисту пам'яті). Якщо ж функція отримає значення, що збігається з допустимою адресою, то функція API залізе в чужу область (наприклад, в ядро ​​Windows) з усіма катастрофічними наслідками, що звідси випливають.

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

3. Перевіряйте тип значення, що повертається.

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

  • DLL-функція, яка не повертає значення (аналог void в 'C'), має бути оголошена як VB Sub.
  • функцію API, що повертає ціле значення (Integer або Long), може бути визначена або як Sub, або як Function, що повертає значення відповідного типу.
  • жодна з функцій API не повертає числа з плаваючою точкою, але деякі DLL можуть повертати такий тип даних.

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

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

5. Не забувайте ініціалізувати рядки.У Win API існує безліч функцій, що повертають інформацію шляхом завантаження даних у рядкові буфери, що передаються як параметр. У своїй програмі ви можете як би все зробити правильно: не забути про ByVal, правильно передати параметри в функцію. Але Windows не може перевірити, наскільки великий розмір виділеного під рядок ділянки пам'яті. Розмір рядка повинен бути достатнім для розміщення всіх даних, які можуть бути вміщені в нього. Відповідальність за резервування буфера необхідного розміру лежить на VB-програміст.

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

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

6. Обов'язково використовуйте Option Explicit.

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

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

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

  • використовуйте покроковий режим налагодження або Debug.Print для перевірки кожного підозрілого виклику функції API. Перевірте результати цих викликів, щоб переконатися, що все в межах норми та функція коректно завершилася;
  • Використовуйте Windows-відладчик типу CodeView та налагоджувальну версію Windows (є у Windows SDK). Ці засоби можуть виявити помилку параметрів та щонайменше визначити, яка функція API призводить до помилки;
  • використовуйте додаткові засоби третіх фірм для перевірки типів параметрів та допустимості їх значень. Такі засоби можуть не тільки знаходити помилки параметрів, але навіть вказати на рядок VB коду, де відбулася помилка.

Крім того, необхідно обов'язково перевіряти результат виконання API-функції.

8. Пам'ятайте, що цілі числа в VB і Windows - не одне й те саме.Насамперед слід пам'ятати, що під терміном «Integer» у VB розуміється 16-розрядне число, у документації Win 32 - 32-разрядное. По-друге, цілі числа (Integer і Long) у VB - це величини зі знаком (тобто один розряд використовується як знак, інші - як мантиса числа), у Windows - використовуються лише невід'ємні числа. Ця обставина потрібно мати на увазі, коли ви формуєте параметр за допомогою арифметичних операцій (наприклад, обчислюєте адресу за допомогою підсумовування деякої бази і зміщення). Для цього стандартні арифметичні функції VB не підходять. Що робити в цьому випадку, ми поговоримо окремо.

9. Уважно стежте за іменами функцій.На відміну від Win16 імена всіх функцій Win32 API є чутливими до точного використання малих і великих літер (у Win16 такого не було). Якщо ви десь застосовуєте маленьку літеру замість великої або навпаки, то потрібна функція не буде знайдена. Слідкуйте також за правильним використанням суфікса A або W у функціях, які застосовують рядкові параметри. (Докладніше про це – див. нижче.)

10. Найчастіше зберігайте результати роботи.Помилки, пов'язані з неправильним використанням DLL і Win API, можуть призводити до аварійного завершення роботи VB-середовища, а можливо, і всієї операційної системи. Ви повинні подбати про те, щоб написаний вами код перед тестовим запуском було збережено. Найпростіше - це встановити режим автоматичного запису модулів проекту перед запуском проекту серед VB.

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

До того ж раніше ми згадували про «підводні» камені для широкого класу DLL. У випадку з Win API все набагато простіше, тому що тут чітко уніфікована форма звернення до цих функцій. При цьому слід мати на увазі такі основні моменти:

  1. Функції Win32 API є функціями, тобто процедурами типу Function (у Win16 API було багато підпрограм Sub). Усе це функції типу Long, тому їх описи записуються у такому виде: Declare Function name ... As Long ‘ тип функції _ визначається явному вигляді

    Declare Function name& ' тип функції _ визначається за допомогою суфікса

    Звернення до функції API виглядає так:

Result& = ApiName& ([ СписокАргументів]
  1. Найчастіше значення функції, що повертається, є кодом завершення операції. Причому ненульове значення означає у разі нормальне завершення, нульове - помилку. Зазвичай (але не завжди) можна уточнити характер помилки за допомогою звернення до функції GetLastError. Опис цієї функції має такий вигляд: Declare Function GetLastError& Lib “kernel32” ()

    УВАГА!При роботі в середовищі VB для отримання значення уточненого коду помилки краще використовувати властивість LastDLLError об'єкта Err, тому що іноді VB обнулює функцію GetLastError у проміжку між зверненням до API та продовженням виконання програми.

    Інтерпретувати код, який повертається GelLastError, можна за допомогою констант, записаних у файлі API32.TXT, з іменами, що починаються з суфіксу ERROR_.

    Найбільш типові помилки мають такі коди:

    • ERROR_INVALID_HANDLE = 6& - неправильний описувач
    • ERROR_CALL_NOT_IMPLEMENTED = 120& - виклик у Windows 9x функції, доступної тільки для Windows NT
    • ERROR_INVALID_PARAMETER = 87& - неправильне значення параметра

    Однак багато функцій повертають значення деякого запитуваного параметра (наприклад, OpenFile повертає значення описувача файлу). У разі помилка визначається якимось іншим спеціальним значенням Return&, найчастіше 0 чи –1.

  2. Win32 API використовують строго фіксовані способи передачі найпростіших типів даних. а) ByVal ... As Long

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

    Б) ByVal ... As String

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

    Перша - резервування пам'яті під рядок проводиться у програмі, що викликає, тому якщо API-функція буде заповнювати рядки, то потрібно перед її викликом створити рядок необхідного розміру. Наприклад, функція GetWindowsDirectory повертає шлях до каталогу Windows, який не повинен займати більше 144 символів. Відповідно звернення до цієї функції має виглядати приблизно так:

    WinPath$ = Space$(144) ' резервуємо рядок в _ 144 символу Result& = GetWindowsDirectory& (WinTath$, 144) _ 'заповнення буфера ' Result& - фактичне число символів в імені _ каталогу WinPath$ = Left$(WinPath, Result&)

    Друга проблема полягає в тому, що при зверненні до API-функції проводиться перетворення вихідного рядка на її деяке внутрішнє уявлення, а при виході з функції - навпаки. Якщо за часів Win16 ця операція полягала лише у додаванні нульового байта наприкінці рядка, то з появою Win32 до цього додалася трансформація двобайтного кодування Unicode в ANSI і навпаки. (Про це докладно йшлося у статті «Особливості роботи з рядковими змінними в VB», Комп'ютерПрес 10'99 та 01'2000). Зараз же тільки зазначимо, що за допомогою конструкції ByVal... As String можна обмінюватися рядками лише із символьними даними.

    В) ... As Any

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

    Г) ... As UserDefinedType

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

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

Приклад звернення до функції API

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

Declare Function lopen Lib “kernel32” _ Alias ​​“_lopen” (_ ByVal lpFileName As String, _ ByVal wReadWrite As Long) As Long Declare Function lread Lib “kernel32” _ Alias ​​“_lread” (_ ByVal hFile As Long, lpBuff _ ByVal wBytes As Long) As Long

У VB їх аналогами - у разі точними - є оператори Open і Get (для режиму Binary). Звернемо відразу увагу використання ключового слова Alias ​​в оголошенні функції - це саме той випадок, коли без нього не обійтися. Ці назви функції в бібліотеці починаються з символу підкреслення (типовий стиль для мови C), що не дозволяється VB.

Операція відкриття файлу може виглядати так:

Const INVALID_HANDLE_VALUE = -1 ' невірне _ значення описувача lpFileName$ = “D:\calc.bas” ' ім'я файлу wReadWrite& = 2 ' режим “читання-запису” hFile& = lopen(lpFileName$, wReadWrite&) _ ' визначаємо = INVALID_HANDLE_VALUE Then _ ' помилка відкриття файлу ' уточнюємо код помилки CodeError& = Err.LastDllError 'CodeError& = GetLastError _ ' ця конструкція не працює End If

Тут слід звернути увагу на два моменти:

  • як значення функції ми отримуємо значення описника файлу. Помилки відповідає значення -1;
  • саме в даному випадку не спрацьовує звернення до функції GetLastError – для отримання уточненого значення помилки ми звернулися до об'єкта Err (про можливість такої ситуації ми говорили вище).

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

Dim MyVar As Single wBytes = lread (hFile&, MyVar, Len(MyVar) ' читання речовинного числа, 4 байти ' wBytes - число фактично прочитаних даних, '-1 - помилка... Type MyStruct x As Single i As Integer End Type Dim MyVar As MyStruct wBytes = lread (hFile&, MyVar, Len(MyVar)) ' читання структури даних, 6 байтів

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

Dim MyVar As String MyVar = Space$(10) 'резервуємо змінну для 10 символів wBytes = lread (hFile&, ByVal MyVar, Len(MyVar)) ' читання символьного рядка, 10 символів

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

Читання вмісту файлу в масиві (для простоти будемо використовувати одновимірний байтовий масив) виконується так:

Dim MyArray(1 To 10) As Byte wBytes = lread (hFile&, MyArray(1), _ Len(MyArray(1))* 10) ‘ читання 10 елементів масиву

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

WBytes = lread (hFile&, MyArray(4), _ Len(MyArray(1))* 5) читання елементів масиву з 4-го по 8-й

Порада 5. Використовуйте Alias ​​для передачіта параметрів As Any

Тут на основі попереднього прикладу ми розкриємо суть четвертої ради Дена Епплмана.

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

Declare Function lreadString Lib “kernel32” _ Alias ​​“_lread” (_ ByVal hFile As Long, ByVal lpBuffer As String, _ ByVal wBytes As Long) As Long

При роботі з цим описом вказувати ByVal при зверненні вже не потрібно:

WBytes = lreadString (hFile&, MyVarString, _ Len(MyVarString)) ‘

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

Declare Function lreadString Lib “kernel32” Alias ​​“_lread” (_ ByVal hFile As Long, lpBuffer() As Byte, _ ByVal wBytes As Long) As Long

Проте звернення

WBytes = lreadArray (hFile&, MyArray(), 10)

неминуче призводить до фатальної помилки програми.

Це продовження розмови про особливості обробки рядкових змінних Visual Basic: VB використовує двобайтну кодування Unicode, Win API - однобайтну ANSI (причому з форматом, прийнятим в С, - з нульовим байтом в кінці). Відповідно при використанні рядкових змінних як аргумент завжди автоматично проводиться перетворення з Unicode в ANSI при виклику API-функції (точніше, DLL-функції) та зворотне перетворення при поверненні.

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

Як відомо, тип String можна використовувати для опису структури користувача. У зв'язку з цим слід пам'ятати таке:

  • Категорично не можна використовувати для звернення до Win API конструкцію наступного виду: Type MyStruct x As Single s As String рядок змінної довжини

    У разі рядка змінної довжини у складі структури передається дескриптор рядка з усіма наслідками у вигляді помилки виконання програми.

  • Можна як елемент структури рядок фіксованої довжини: Type MyStruct x As Single s As String*8 ‘ рядок фіксованої довжини End Type

При цьому провадиться відповідне перетворення кодувань.

І останнє зауваження: застосовувати масив рядкових змінних (як фіксованої, так і змінної довжини) при зверненні до функції API не можна в жодному разі. Інакше поява «нелегальної операції» буде гарантованою.

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

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

Думка автора така: серйозне заняття програмуванням вимагає від розробника володіння принаймні двома інструментами. Зрозуміло, в сучасних умовах чіткого поділу праці дуже складно бути відмінним експертом навіть за двома системами, тому більш логічною є схема «основної та допоміжної мови». Ідея тут полягає в тому, що навіть поверхове знання «допоміжної» мови (написання досить простих процедур) може помітно підвищити ефективність застосування «основної». Зазначимо, що знання VB хоча б як допоміжне є сьогодні практично обов'язковою вимогою для професійного програміста. До речі, за часів DOS для будь-якого програміста, зокрема Basic, було вкрай бажаним знання основ Асемблера.

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

При вивченні міжпроцедурного інтерфейсу слід звернути увагу на такі можливі «підводні камені»:

  • Різні мови можуть використовувати різні угоди про правила написання ідентифікаторів. Наприклад, часто використовується знак підкреслення початку імені процедури, що заборонено в VB. Ця проблема легко вирішується за допомогою ключового слова Alias ​​в операторі Declare (див. приклад поради 2.3).
  • Може бути використана різна послідовність запису аргументів, що передаються в стек. Наприклад, за часів DOS (чесно зізнаюся – не знаю, як це виглядає зараз у середовищі Windows), C записував аргументи з кінця списку, інші мови (Fortran, Pascal, Basic) – з початку.
  • За замовчуванням використовуються різні принципи передачі параметрів - за посиланням або значенням.
  • Різні принципи зберігання рядкових змінних. Наприклад, в C (так само як у Fortran і Pascal) довжина рядка визначається нульовим байтом у її кінці, а в Basic довжина записується у явному вигляді в дескрипторі рядка. Зрозуміло, потрібно пам'ятати можливість використання різних кодувань символів.
  • При передачі багатовимірних масивів слід пам'ятати, що можливі різні варіанти перетворення багатовимірних структур на одновимірні (починаючи з першого індексу або з останнього, стосовно двовимірних масивів - «по рядках» або «по стовпчикам»).

З урахуванням цього можна сформулювати такі рекомендації:

  • Використовуйте найпростіші, перевірені способи передачі аргументів у функції DLL. Стандарти, прийняті для Win API, цілком підходять як зразок.
  • У жодному разі не передавайте масиви рядкових змінних.
  • Дуже уважно використовуйте передачу простих рядкових змінних та багатовимірних масивів.
  • Обов'язково спеціальним чином перевіряйте працездатність механізму передачі аргументів у процедуру, що викликається, і назад. Напишіть спеціальний тест для перевірки даних. Окремо перевірте правильність передачі кожного аргументу. Наприклад, якщо у вас є процедура з кількома аргументами, перевірте спочатку коректність передачі кожного параметра для варіанта з одним аргументом, а потім - для всього списку.

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

Отже: використовуйте DLL функції. Але зберігайте пильність.

Комп'ютерПрес 9"2000

Ця стаття адресована таким же, як і я новачкам у програмуванні на С++, які з волі випадку або за бажанням вирішили вивчати 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) все заховано в секцію private класу.
Для їх отримання досить описати прості методи-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”ами.
Прошу до посту поставитися з розумінням. Конструктивна критика, звичайно ж, вітається. Операційні системи (ОС)