Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

Úvod do programování ve Win API

3.1.2002

V minulém, úvodním díle jsme se kromě teoretického úvodu dostali pouze k vytvoření té asi nejjednodušší Win32 aplikace, která pro zobrazení textu použila API funkci MessageBox. Dnes již se začneme (pomalu ale jistě) zabývat skutečnou aplikací. Skutečnou v tom smyslu, že bude mít alespoň jedno běžné okno, tzv. hlavní okno aplikace. A toto okno na rozdíl od uvedeného dialogu MessageBox, i přesto, že nebude modální, bude moci zůstat v systému tak dlouho, jak si budeme přát, tedy i poté co ztratí fokus. Což je pro každého samozřejmě jasná záležitost, ale leckdo neví proč tomu tak je a jak to funguje uvnitř. Nejdříve bychom si měli říci trochu teorie o tom, jak jsou vlastně programy ve Windows řízeny a ovládány. Pod pojem programy budu mít dále na mysli pouze běžné Win32 aplikace, tedy nikoli konzolové aplikace běžící v okně, nebo nějaké speciální služby. Je to samozřejmě ne zcela přesné zjednodušení, ale pro výklad ho použijme. Takže: pod Windows jde o tzv. "událostmi řízené programování". Jde o to, že o jakékoli události v systému jsou posílány jednotlivým oknům (nebo více oknům "současně") jedna nebo i více zpráv, které tuto událost nějak popisují. Takovou událostí je jakákoli "myší akce", stisk klávesy, uplynutí nějakého aplikací definovaného timeru a mnoho dalších typů zpráv. A tyto zprávy jsou pak doručovány oknu (nebo oknům), kterých se nějak dotýkají, to znamená, že toto okna na ně musí, nebo "může pokud chce", nějak reagovat. A reakce na tyto zprávy (události) je vlastně ono událostmi řízené programování. Významným rysem takového programu je, že je schopen čekat na podněty z nejrůznějších směrů. V DOSovém programu většina aplikací po většinu doby svého "života" prostě čekala na klávesnicový vstup uživatele. Na něj nějak zareagovala a opět se čekalo. Pokud jsme chtěli současně "být na příjmu" i z jiných směrů, museli jsem nějak periodicky "osahávat" ostatní porty, ze kterých by mohlo "něco přijít". To byla pro většinu programátorů poměrně složitá záležitost. Dále neexistoval multitasking, takže čekající program stále zabíral prakticky 100% výkonu procesoru a počítač byl jinak "k nevyužití". Nyní máme Windows s multitaskingem a událostmi řízeným programováním. Brzy se sami naučíte velice jednoduše to, jak zařídit abychom byli připraveni přijmou uživatelský vstup "kdykoli a kdekoli". Uživatel může tutéž akci vyvolat myší, horkou klávesou, z nabídky, když budeme chtít tak třeba i joystickem apod. A kromě toho bude náš program současně připraven reagovat na různé systémové události či změny, které se ho mohou nějak týkat. Je to například změna rozlišeni obrazovky, změna barevné palety, požadavek na ukončení Windows nebo odhlášení uživatele apod. Pro představu o množství zpráv si můžete spustit program Spy++, který je součástí instalace Visual C++, popř. WinSight, který je v C++ Builderu (nebo samozřejmě jiný podobný). Nastavte si monitorování všech zpráv všem oknům v systému. A stačí pohnout myší (půjde to hůře ten monitor samozřejmě bere velké procento výkonu procesoru) a pomaleji odbíhající seznam zpráv se rázem rozeběhne jak o závod. Během několika sekund budete mít tisíce, spíše desetitisíce zpráv. A i když dáte "ruce pryč od počítače", stále tam budou, i když podstatně pomaleji, běžet zprávy různých timerů a dalších systémových zpráv. K čemu nám taková zpráva je a co s ní? Jak údaje z ní získat se samozřejmě dozvíme, nyní se pro představu podívejme třeba na zprávu oznamující stlačení levého tlačítka myši. Tato zpráva má identifikátor WM_LBUTTONDOWN. Tyto identifikátory zpráv (většina těch běžných začíná na WM_ (Windows Message)) jsou definovány v hlavičkových souborech, ty běžné konkrétně ve winuser.h, který sám je "inkludován" v souboru "windows.h", což je základní hlavičkový soubor, který musí být v každém Win32 programu. Naše zpráva konkrétně je definovaná takto:
#define WM_LBUTTONDOWN 0x0201
Podívejme se nyní na tuto zprávu do dokumentace. Zjistíme, že tato zpráva, stejně jako všechny ostatní má dva parametry: wParam typu WPARAM a lParam typu LPARAM1). Oba tyto parametry jsou ve Win32 32-ti bitová celá čísla. Řeknete si možná, že do 2 čísel, byť 32-ti bitových moc informací nevměstnáme. Jak uvidíte, většinou to co nás v tom okamžiku bude zajímat, se tam vejde a pokud ne, tak u mnoha zpráv je lParam adresou nějaké další datové struktury, která již může být libovolně velká. Vraťme se k naší zprávě WM_LBUTTONDOWN. Dokumentace nám říká, že wParam je indikátor virtuální klávesy. Zde je třeba vědět, že onou virtuální klávesou může být také některé z tlačítek myši. Pokud by nás totiž zajímalo, zda v okamžiku události byla současně stlačena některá "speciální" klávesa či nějaké další tlačítko myši, můžeme to zjistit z tohoto parametru. Ten může totiž být kombinací následujících hodnot:
  • MK_CONTROL - je stlačena klávesa <Ctrl>
  • MK_LBUTTON- je stlačené levé tlačítko myši
  • MK_MBUTTON - je stlačené střední tlačítko myši
  • MK_RBUTTON -  je stlačené pravé tlačítko myši
  • MK_SHIFT - stlačena klávesa <Shift>
  • MK_XBUTTON1 - (Windows 2000 a výše) stlačen 1. tzv. x-button (další tlačítka např. na Microsoft IntelliMouse)
  • MK_XBUTTON2 - totéž s x-buttonem 2
Stejný význam parametru wParam je u všech "příbuzných myších zpráv", tedy například WM_LBUTTONUP (levé tlačítko bylo uvolněno) nebo WM_RBUTTONDOWN (pravé tlačítko bylo stlačeno). Nyní se podívejme na lParam, který nás bude asi zajímat ve většině případů. Nese informace o souřadnicích myši v okamžiku události (což nemusí být totožné s těmi souřadnicemi, na kterých je myš v okamžiku, kdy zprávu dostaneme, pokud je systém dostatečně vytížen tak může dojít k výrazné prodlevě). Konkrétně x-ová souřadnice je v dolním WORDu parametru lParam, y-ová zase v horním. Pro jejich "lidštější" získání máme k dispozici různá makra, například makro MAKEPOINTS, které můžeme použít takto:
POINTS ptMouse;
ptMouse = 
MAKEPOINTS(lParam);
int x = ptMouse.x;
int y = ptMouse.y
Trochu s předstihem ještě tolik, že často nastává situace, kdy (z hlediska uživatele) jedna událost vyvolá více zpráv. Jako příklad lze uvést uvolnění pravého tlačítka myši, při němž je poslána zpráva WM_RBUTTONUP a "současně" WM_CONTEXTMENU (tedy podnět k vyvolání kontextové nabídky). Ta "současnost" je samozřejmě v uvozovkách. Můžete si spustit již zmíněný program Spy++ či WinSight a zjistit, která z uvedených zpráv přijde dříve. Možná si řeknete: "Proboha, tolik zpráv, to musím myslet na všechno a všechno nějak ošetřovat?". Samozřejmě nikoliv. Ty stovky a tisíce zpráv, na které nechceme v programu nijak specificky reagovat, prostě necháme systému, ať se o ně postará. Příkladem může mýt třeba tažení okna za titulek. S nějakou implementací posunu okna v závislosti na myších zprávách v titulku si vůbec nebudeme dělat těžkou hlavu, pouze musíme příslušné zprávy "pustit" k "defaultnímu" zpracování, a Windows za nás udělá zbytek. Některým možná již připadá, že na úvod příliš teoretizuji a vykládám banální záležitosti. Ale tento seriál by měl být určen i těm, kteří třeba Windows prakticky neznají a chtějí problematiku pochopit. Většinou bývá lepší vědět jak a proč to tak funguje, než jenom jak to udělat. Když nevím v kterém parametru zprávy jsou souřadnice, podívám se do dokumentace, ale když neznám princip, budu jen bezmyšlenkovitě opisovat nějaké (snad) fungující ukázky kódu a doufat, že to nějak rozchodím. Ale nyní se již vrhneme i do praxe. Vytvoříme si nový projekt. Ve Visual C++ ("File -> New ->Projects") typu Win32 Application, dále zvolíme "prázdný projekt", vše si vytvoříme sami. V C++ Builderu zvolíme "File -> New -> "Console Wizard".

win_api_2

Zde odškrtneme volby Use VCL a "Console Application", jak vidíme na obrázku. C++ Builder vám nyní vygeneruje hlavní zdrojový soubor aplikace i s funkcí WinMain. Ve Visual C++ si vše přidáme sami. Přidejme si tedy do projektu ("Project -> AddTo Project -> New" -> C/C++ Header File") hlavičkový soubor, nazvaný třeba "main.h" a obdobně zdrojový soubor (C/C++ Source File) nazvaný main.cpp. Zde si řekněme o významu koncovky. Ve standardním nastavení platí, že pokud zdrojovému souboru dáme koncovku .c, kompilátor ho považuje za céčko bez rozšíření C++. Pokud použijeme koncovku .cpp, můžeme použít i jazyk C++. Já jsem použil tu možnost s rozšířením na jazyk C++, i když v tomto "základním API"  bychom si vystačili s jazykem C. Dovolíme si však ten luxus občas použít některé konstrukce specifické pro C++, jako pohodlnější definice proměnných uvnitř kódu apod. Navíc dnes již existuje jako součást Windows SDK sada tříd GDI+, které jsou vlastně také součástí Win API. Konkrétně jde o rozšíření grafického rozhraní GDI, které již podporuje formáty jako .jpg či .png a má i další "fajnovosti"... Ale my musíme nyní začít od základů. Nejdříve si do našeho hlavičkového souboru "main.h" přidáme základní hlavičkový soubor <windows.h>
#include <windows.h>
Do zdrojovéhp souboru "main.cpp" si vložíme "main.h" a  můžeme začít. Každý standardní program ve Windows musí mít jednu "hlavní" funkci, ve Windows nazvanou WinMain.V C++ Builderu ji již máte vygenerovanou. Ve Visual C++ si jí napíšeme takto.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE 
hPrevInst, LPSTR lpCmdLine, int nShow)
{
  return 0;
}
Co znamenají jednotlivé parametry?
  • hInstance - Handle instance je 32-bitové číslo. Z pohledu programátora se na něj lze dívat jako na identifikaci kódu programu, neboť toto číslo je často potřeba zadat jako parametr různých funkcí. Ve skutečnosti představuje adresu počátku kódu, resp. exe souboru v paměťovém prostoru aplikace. Adresu kam je tento kód umístěn určuje v zásadě linker, a ve výchozím nastavení bude její hodnota pravděpodobně hexadecimálně 0x00400000.
  • hPrev - tento parametr je zde pouze pro zpětnou kompatibilitu zdrojového kódu s Win16 programy. Tehdy, pokud došlo k tomu, že stejný program (.exe soubor) byl spuštěn vícekrát, tak v tomto parametru byl uložen handle předchozí spuštěné instance programu, a my jsme mohli snadno na tuto skutečnost nějak reagovat. Nyní je tento parametr vždy NULL, což však vůbec neznamená, že nelze zjistit předchozí spuštění téže aplikace. Před časem jsem zde psal článek na toto téma, takže ti znalejší se mohou s tímto tématem obeznámit podrobněji.
  • lpCmdLine - ukazatel na "řetězec" obsahující parametry příkazové řádky
  • nShow - hodnota určující požadavek na způsob zobrazení hlavního okna aplikace. To znamená, zda se má spustit v normálním stavu, mnaximalizovaná, minimalizovaná apod. Podrobněji se k tomuto samozřejmě vrátíme.
Vzhledem k tomu, že vytvoření "skutečné aplikace" s vlastním oknem již není tak banální jako zavolání MessageBoxu z úvodního dílu, necháme si pokračování do příště. Na závěr ještě pár slov a typech definovaných ve Windows SDK. Když se podíváte na funkci WinMain, zjistíte, že je zde typ LPSTR. Ve skutečnosti jde o pouhý "typedef" znamenající v tomto případě CHAR*, tedy běžný ukazatel na "céčkovský řetězec". Název může být ze slov "Long Pointer (to) String". Podobných "typedefů" je ve Windows mnoho, proto se nezalekněte při pohledu na ně. K dalším specifickým typům ve Windows se ještě dostaneme. 1)Nakonec jen vysvětlení názvů typů parametrů zpráv, se kterými se budeme setkávat na každém kroku. Tyto názvy pocházejí ještě z doby 16-ti bitových Windows, kde wParam byl 16-ti bitový typu WORD, od toho wParam (= WORD Param) a lParam byl i tam 32-ti bitový (tedy LONG - odtud lParam).