DirectX (9.)
V této lekci se seznámíme se třídou CInput, která zapouzdřuje práci s objekty
DirectInput. Postupně tuto třídu rozvineme do rozsáhlé struktury a vyzkoušíme si
práci s knihovnou DLL, protože celý modul DirectInput oddělíme do samostatné
knihovny. Něco málo o knihovnách jsem zmínil minule a dnes to rozšíříme.
Výsledkem tak bude samostatná knihovna DLL zapouzdřující funkčnost DirectInput.
9.1. Vytvoření nového projektu
Asi nejlepší bude, když nový projekt knihovny vložíte do již rozdělaného
projektu DirectDraw. To proto, abychom mohli všechno hned testovat. Jak se to
udělá jsem zmínil v minulé lekci o VC++, ale pro ty, kteří článek nečetli nebo
se k nim nedostal Chip, to zopakuji.
Za prvé vytvoříme prázdný projekt Blank Workspace třeba se jménem DirectX. Z
menu File vyberte položku New. Po zobrazený dialog vyplňte nějak
takto:

V místě, které jste zadali v políčku Location se vytvoří adresář. Do
tohoto adresáře kompletně zkopírujte projekt z předchozích lekcí o DirectDraw.
Důležité je, aby DirectDraw zůstalo v samostatném podadresáři pod DirectX.
Po té z vývojového prostředí přidáme tento projekt jako již existující. Z menu
Project z volte poslední položku Insert Project into Workspace a v dialogu
vyberte soubor DirectDraw.dsp v podadresáři DirectDraw. Všimněte si změny v
ClassView.
Novou knihovnu Input nemusíte nutně spojovat s projektem DirectDraw, ale můžete
si vytvořit zcela jinou aplikaci. Knihovna Input.dll bude nezávislá
(alespoň prozatím). Nyní tedy vložme knihovnu:
Z menu File vyberte položku New. Zobrazený dialog vyplňte nějak
takto:

Zde je důležité abyste projekt přidali do již otevřené Workspace. Projekt
knihovny se opět vytvořil v samostatném adresáři Input. Dále nastavte rozšířenou
knihovnu DLL, aby se nám lépe pracovalo s exporty a případně se zdroji:

Nyní si všimněte, že do okna ClassView přibyla položka Input. Dále trochu
upravíme vlastnosti obou projektů, aby se například kompilovaly ve správném
pořadí a do stejného adresáře Release a Debug celé Workspace.
Z menu Project vyberte položku Settings. Nastavte na kartě Link společný adresář
pro výstupní soubory (EXE a DLL). Toto nastavení musíte udělat zvlášť pro oba
projekty a navíc pro verze Release a Debug (čili 4x).


Dále nastavíme závislosti. Musíme zaručit že projekt Input se bude kompilovat
před DirectDraw a že Input bude přilinkován k DirectDraw. I to jsme si ukazovali
minule. Z menu Project vyberte položku Dependencies a dialog
nastavte takto:

Vidíte závislost projektu DirectDraw na projektu Input.
To je k nastavení projektů zatím vše. Za chvilku ještě přidáme některé dynamické
knihovny potřebné pro samotné DirectInput jako tomu bylo u DirectDraw. Před
kompilací se ujistěte, že máte DirectDraw projekt nastavený jako výchozí (nebo
aktivní). Zkuste si projekt zkompilovat a uvidíte, že nejprve se sestaví projekt
Input a teprve pak DirectDraw.
9.2. Třída CInput
Konečně se dostáváme k programování. Jak už bylo řečeno, jádrem knihovny bude
třída CInput. Tuto třídu lze exportovat dvěma
způsoby. Za prvé můžete exportovat celou třídu. Klient, který knihovnu chce
použít pak musí vytvořit objekt typu CInput a
teprve pak volat jednotlivé metody. Druhý způsob je podle mě z uživatelského
hlediska snazší, ale z vaší strany vyžaduje pár řádků navíc. V samotné knihovně
vytvoříme globální objekt CInput a poté budeme
exportovat pouze globální funkce, které uvnitř pouze volají metody globálního
objektu. Tento způsob také zaručí, že uživatel nevytvoří více objektů
CInput, což je nežádoucí.
Přidat třídu do projektu již umíme. Z menu Insert zvolte položku New class.
TIP: Abyste přidali třídu do projektu Input musí být tento projekt
aktivní. Abyste nemuseli přepínat mezi projekty, stačí využít kontextové menu v
ClassView. Kliknete-li pravým tlačítkem na projekt Input, můžete přímo vybrat
volbu New class.
Následující dialog vyplňte přibližně takto:

Právě v tuto chvíli budeme potřebovat knihovny DirectInput. Jsou dvě:
dinput8.dll a dxguid.dll.
V menu Project volte položku Settings. Opět na kartě Link
přidejte do políčka Objects/library modules jména knihoven importů těchto
dvou knihoven: dinput8.lib a
dxguid.lib. Nastavení proveďte i pro Release. Dále je potřeba vložit
hlavičkový soubor dinput.h nejlépe do souboru
stdafx.h projektu Input (nejlépe se k němu
dostanete pře FileView) Nyní máme zajištěno, že pokud použijeme některé
objekty a funkce DirectInput, bude je kompilátor i linker znát.
Následující tabulky popisují třídu CInput:
Členské proměnné |
Typ proměnné |
Název proměnné |
Popis |
BOOL |
m_bInit |
TRUE pokud je systém inicializovaný jinak
FALSE. To jest ochrana proti vícenásobném volání inicializační metody
a proti neoprávněnému volání ostatních metod. |
LPDIRECTINPUT8 |
m_lpDI |
Ukazatel na objekt DirectInput. Tento
objekt vytvoříme v inicializační metodě. |
LPDIRECTINPUTDEVICE8 |
m_lpDIDKeyboard |
Ukazatel na objekt klávesnice. Používá se
ke stažení informací o aktuálním stavu klávesnice (naplní pole
jednotlivých kláves, ze kterého je pak možno určit, která klávesa je
stisknuta a která nikoliv). |
LPDIRECTINPUTDEVICE8 |
m_lpDIDMouse |
Ukazatel na objekt myši. Používá se ke
stažení informací o aktuálním stavu myši (poloze a tlačítkách). |
BYTE |
m_arKeyboard[256] |
V tomto poli se právě ukládá stav
jednotlivých kláves. |
DIMOUSESTATE |
m_MouseState |
Struktura obsahující aktuální stav myši
(informace o tlačítkách, pohybu apod.) |
BOOL |
m_IsHandled[256] |
Do tohoto pole se ukládá informace o tom,
zda-li je klávesa stisknuta poprvé, či jestli je držena. |
BOOL |
m_bIsMouseHandled[2] |
Obdobné pole jako předchozí, pouze pro dvě
tlačítka myši. |
CPoint |
m_ptCursor |
Aktuální souřadnice kurzoru. |
CSize |
m_szRes |
Hodnoty x a y popisují aktuální rozlišení.
Tuto hodnotu budeme inicializovat v inicializační metodě a slouží nám
k tomu, abychom kurzor udrželi ve správných mezích. |
HICON |
m_hCursor |
Handle na aktuální kurzor. |
int |
m_iMouseSensitivity |
Sensitivita myšky čili citlivost. |
BOOL |
m_bShowCursor |
Informace o tom, zda-li má být kurzor
vidět (TRUE) či nikoliv (FALSE). |
Členské funkce - metody |
Veřejné
funkce - funkce které budou exportovány z knihovny |
Návratová hodnota |
Název funkce a
parametrů |
Popis metody i
parametrů |
HRESULT |
CreateDirectInputSystem(HINSTANCE hInst,
HWND hWnd, CSize csResolution) |
Inicializuje objekt DirectInput, objekt
klávesnice a objekt myši. Nastavuje chování aplikace vůči svému okolí
a ukládá si rozlišení. |
HRESULT |
RestoreDevices() |
Pokud si pamatujete na obnovování ztracených povrchů,
pak tato metoda dělá něco podobného, ale pro myš a klávesnici. |
BOOL |
IsRButtonDown(BOOL _Handling = TRUE) |
Vrací TRUE pokud je stisknuté pravé tlačítko myši.
|
BOOL |
IsLButtonDown(BOOL _Handling = TRUE) |
Vrací TRUE pokud je stisknuté levé tlačítko myši. |
BOOL |
IsRButtonUp() |
Vrací TRUE pokud není stisknuté pravé tlačítko myši.
|
BOOL |
IsLButtonUp() |
Vrací TRUE pokud není stisknuté levé tlačítko myši.
|
BOOL |
IsKeyDown(int Key, BOOL bHandle) |
Vrací TRUE pokud je stisknutá požadovaná klávesa (Key). |
void |
ProcessInput() |
Stahuje data z klávesnice a myši. Posouvá kurzor
myšky. |
void |
UpdateCursor(HDC hdc) |
Vykresluje kurzor na obrazovku. Požaduje HDC (handle
kontext zařízení) okna nebo povrchu DirectDraw. |
CPoint |
GetCursor() |
Vrací aktuální pozici kurzoru. |
void |
ShowCursor(BOOL bShow) |
Pokud je parametr bShow, kurzor se zviditelní jinak
zůstane skrytý. |
HRESULT |
LoadCursor(HICON hCursor) |
Nastavuje aktuální zobrazovaný kurzor. |
Soukromé funkce |
Návratová hodnota |
Název funkce a
parametrů |
Popis metody i
parametrů |
void |
Clean() |
Provádí čistící práce při ukončení aplikace nebo při
násilném ukončení v důsledku chyby. |
HRESULT |
MoveCursor(int dx, int dy) |
Pouze přičítá přírůstky x-ové a y-ové souřadnice
kurzoru k aktuální pozici kurzoru. Přírůstky jsou vytaženy ze stavové
struktury myši. |
Ve třídě CDisplay byla funkce
CreateFullscreenDisplay(). My si podobnou funkci vytvoříme i naší třídě.
Třída bude mít následující deklaraci:
HRESULT CreateDirectInputSystem(HINSTANCE
hInst, HWND hWnd, CSize csResolution);
Předáváme ji tři parametry, které později využijeme při vytváření objektu
DirectInput:
HINSTANCE hInst |
handle instance aplikace |
HWND hWnd |
handle okna aplikace |
CSize
csResolution |
struktura naplněná informací o
rozlišení |
Přidejte tuto funkci do třídy. Máme dva způsoby, jak přidat
novou metodu. Buď opět využijeme kontextového menu, ale nyní přímo na třídě,
nebo to uděláme ručně. Minule jsem vám taky poradil, abyste si vytvářeli funkce
Clean() u složitějších tříd. Tuto funkci s výhodou
využijeme i v naší nové třídě.
Nyní doplníme kód funkce
CreateDirectInputSystem():
HRESULT CInput::CreateDirectInputSystem(HINSTANCE
hInst, HWND hWnd, CSize csResolution)
{
HRESULT dwRet = 1;
//
// You cannot call this function twice
if(!m_bInit) {
//
// Creation of direct input object
dwRet =
DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&m_lpDI,
NULL);
if(dwRet != DI_OK) {
TRACE("Cannot create
direct input object %d\n", dwRet);
Clean();
return dwRet;
}
//
// Create keyboard object
dwRet = m_lpDI->CreateDevice(GUID_SysKeyboard, &m_lpDIDKeyboard, NULL);
if(dwRet != DI_OK)
{
TRACE("Cannot
create keyboard object %d\n", dwRet);
Clean();
return dwRet;
}
//
// Create mouse object
dwRet = m_lpDI->CreateDevice(GUID_SysMouse, &m_lpDIDMouse, NULL);
if(dwRet != DI_OK)
{
TRACE("Cannot
create mouse object %d\n", dwRet);
Clean();
return dwRet;
}
//
// Set data format for both
devices
dwRet = m_lpDIDMouse->SetDataFormat(&c_dfDIMouse);
if(dwRet != DI_OK)
{
TRACE("Cannot
set data format for mouse %d\n", dwRet);
Clean();
return dwRet;
}
dwRet = m_lpDIDKeyboard->SetDataFormat(&c_dfDIKeyboard);
if(dwRet != DI_OK)
{
TRACE("Cannot
set data format for keyboard %d\n", dwRet);
Clean();
return dwRet;
}
//
//
Set cooperative level for both devices
dwRet = m_lpDIDKeyboard->SetCooperativeLevel(hWnd, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE);
if(dwRet != DI_OK)
{
TRACE("Cannot
set cooperative level for keyboard %d\n", dwRet);
Clean();
return dwRet;
}
dwRet = m_lpDIDMouse->SetCooperativeLevel(hWnd,
DISCL_NONEXCLUSIVE|DISCL_FOREGROUND);
if(dwRet != DI_OK)
{
DXTRACE6("Cannot set cooperative level for mouse %d\n",
dwRet);
Clean();
return dwRet;
}
//
// Acquire mouse and keyboard
dwRet = m_lpDIDKeyboard->Acquire();
if(dwRet != DI_OK)
{
DXTRACE6("Cannot acquire keyboard %d\n", dwRet);
Clean();
return dwRet;
}
dwRet = m_lpDIDMouse->Acquire();
if(dwRet != DI_OK)
{
TRACE("Cannot
acquire mouse %d\n", dwRet);
Clean();
return dwRet;
}
// Inicializace nekterych vnitrnich atributu
m_csResolution
= csResolution;
m_bInit =
TRUE;
}
return dwRet;
}
Předchozí kód možná vypadá poněkud složitě, ale ve skutečnosti je to všechno
lehké. První podmínka je pouze z důvodu ochrany před vícenásobným volání metody.
Zkrátka pokud se uživatel pokusí víckrát volat tuto i další metody, nic se
nestane a je vrácena hodnota 1. Všechno to zajišťuje proměnná typu
BOOL m_bInit. V prvním kroku vytváříme objekt
DirectInput. K tomu slouží funkce DirectInput8Create()
s pěti parametry:
HINSTANCE
hinst |
Handle na instanci aplikace. Právě u tohoto parametru
využijeme vstupní parametr funkce CreateDirectInputSystem() |
DWORD
dwVersion |
Požadovaná verze vytvářeného objektu. U tohoto
parametru využijeme symbolické konstanty
DIRECTINPUT_VERSION. Pokud bychom chtěli jinou verzi než 8.0,
museli bychom tuto konstantu sami definovat ještě před vložením
hlavičkového souboru input.h. Napsali
bychom třeba
#define DIRECTINPUT_VERSION 0x0700
pro verzi 7.0 apod. Pokud chceme
použít verzi 8.0 (což chceme), je dobré explicitně konstantu definovat
také, protože jinak ses nám bude ve výstupním okně objevovat varovná
hláška. |
REFIID
riidltf |
Unikátní identifikátor rozhraní, které po funkci
chceme. Chceme rozhraní objektu DirectInput, takže posíláme hodnotu
IID_IDirectInput8. |
LPVOID*
ppvOut |
Proměnná, do které se uloží adresa požadovaného
rozhraní. Je to stejné jako u DirectDraw, posíláme ukazatel na
ukazatel. |
LPUNKNOWN
punkOuter |
Tento parametr se využívá, pouze pokud chceme použít
agregaci COM. My pošleme hodnotu NULL. |
Funkce vrací hodnotu DI_OK (0) pokud je vše v pořádku. Všimněte si logiky
odhalování chyb. Pokud by se něco pokazilo, ihned je volána metoda
Clean(), která uvolní rozhraní a funkce je ukončena
s tím, že nám zapíše kód chyby do výstupního okna. V DirectX existuje pár funkcí
a maker, které jsou schopny odhalit o jakou chybu se jedná a vrací řetězec
čitelný pro člověka.
V dalších krocích provádíme tytéž kroky pro myš a klávesnici. Pomocí funkce
CreateDevice() vytvoříme objekt myši a objekt
klávesnice. My si pouze ukládáme ukazatel na rozhraní těchto objektů a pokud je
toto rozhraní uvolněno funkcí Release(), je i
objekt myši nebo klávesnice uvolněn. Všimněte si také, že metoda
CreateDevice() je členská funkce rozhraní objektu
DirectInput.
CreateDevice() má tři parametry:
REFGUID rguid |
Unikátní identifikátor zařízení, pro které chceme
vytvořit objekt. Pokud chceme pouze myš nebo klávesnici můžeme použít
předdefinované hodnoty GUID_SysMouse nebo
GUID_SysKeyboard. Pokud ovšem chcete jiná
zařízení, musíte použít funkci EnumDevices(),
abyste zjistili, co je právě připojeno k PC. |
LPDIRECTINPUTDEVICE *lplpDirectInputDevice |
Proměnná, do které se uloží ukazatel našeho rozhraní.
Opět je to stejné jako všude jinde: předáváme ukazatel na ukazatel. |
LPUNKNOWN
pUnkOuter |
Poslední parametr je stejný jako poslední parametr
předchozí funkce. Pošleme NULL. |
Nyní máme zinicializované všechny potřebná rozhraní a zbývá pár funkcí nutných
pro správný chod. Především je to funkce SetDataFormat(),
SetCooperativeLevel() a Acquire(). Všechny
tyto metody voláme pro každé zařízení zvlášť.
Funkce SetDataFormat() pouze nastavuje, v jakém
formátu budeme stahovat data z klávesnice nebo myšky. Funkce má jeden parametr,
který představuje buď předdefinovaný nebo uživatelský formát stáhnutých dat.
Předdefinované formáty jsou následující (v pravém sloupci jsou příslušné
struktury, které budeme používat):
Například rozdíl mezi
DIMOUSESTATE a
DIMOUSESTATE2 je takový, že struktura
DIMOUSESTATE2 obsahuje informace o osmi
tlačítkách myši, zatímco struktura
DIMOUSESTATE jen o čtyřech.
Všimněte si také, že pro klávesnici máme již zmiňované pole bytů.
Více se o těchto datových strukturách dovíte, až je budeme přímo používat.
Funkce SetCooperativeLeve() nastavuje chování
našeho zařízení (objektu zařízení) vůči ostatním objektům téhož zařízení a vůči
okolním aplikacím. Je to podobné jako u DirectDraw. Funkce má dokonce velmi
podobné parametry:
HWND hWnd |
Handle na okno aplikace, to jest druhý parametr naší
funkce CreateDirectInputSystem().
|
DWORD dwFlags |
Zde je to o trochu složitější. Tento parametr se může
skládat z více hodnot. Je pět hodnot: DISCL_BACKGROUND
pro zajistí to, že přístup k zařízení může získat i okno, které není
aktivní, čili je v pozadí. Pokud programujeme s DirectDraw,
pravděpodobně budeme používat fullscreen a tam máme pouze jedno naše
okno na popředí takže raději nastavíme druhou hodnotu
DISCL_FOREGROUND. Hodnota
DISCL_EXCLUSIVE zajišťuje exkluzivní
přístup zařízení. Pokud získáme exkluzivní přístup, žádná jiná
aplikace nemůže získat rovněž exkluzivní přístup. Naproti tomu stojí
DISCL_NONEXCLUSIVE, která zajišťuje běžný
přístup a který může získat více aplikací najednou. Abychom se vyhnuli
konfliktům nastavíme hodnotu neexkluzivní. Poslední hodnota
DISCL_NOWINKEY pouze zabraňuje použití
klávesy s okénkem, pro vyskočení z aplikace. |
Poslední metodou Acquire() získáme konečně přístup
k datům zařízení. Pokud vyskočíme mimo z naší aplikace, je toto právo přístup
zrušeno a po zpětném navrácení se musí opět zavolat funkce
Acquire(), abychom opět získali přístup k zařízení (tuto činnost bude
provádět funkce RestoreDevices()).
9.3. Závěr
A to je pro tuto lekci vše. Příště budeme pokračovat v implementaci třídy
CInput a doufejme, že stačíme celý systém spustit.
Do budoucna bych chtěl uvést trochu komplexnější příklad grafického menu, které
pracuje s DirectDraw a s DirectInput. Budu rád za vaše dotazy a připomínky.
Těším se příště nashledanou.
Jiří Formánek
|