Sdílet prostřednictvím


Programování DirectX s COM

Model COM (Microsoft Component Object Model) je objektově orientovaný programovací model používaný několika technologiemi, včetně hromadné plochy rozhraní API DirectX. Z tohoto důvodu budete (jako vývojář DirectX) nutně používat com při programování DirectX.

Poznámka

Téma Využití komponent modelu COM pomocí C++/WinRT ukazuje, jak vlastně používat rozhraní DirectX API (a jakékoli COM API) pomocí C++/WinRT. To je zdaleka nejpohodlnější a doporučená technologie, která se má použít.

Alternativně můžete použít surové rozhraní COM, a právě o tom je toto téma. Budete potřebovat základní znalosti principů a programovacích technik, které se týkají využívání rozhraní COM API. Ačkoli má COM pověst obtížného a složitého, COM programování vyžadované většinou u aplikací DirectX je přímočaré. Je to proto, že budete využívat objekty MODELU COM poskytované rozhraním DirectX. Nemusíte vytvářet vlastní objekty COM, což je obvykle místo, kde vzniká složitost.

Přehled komponent MODELU COM

Objekt COM je v podstatě zapouzdřená komponenta funkcí, kterou mohou aplikace použít k provádění jedné nebo více úloh. Pro nasazení se jedna nebo více komponent COM zabalí do binárního souboru označovaného jako COM server; častěji knihovna DLL.

Tradiční knihovna DLL exportuje bezplatné funkce. Server COM může provést totéž. Komponenty COM uvnitř serveru COM však zveřejňují rozhraní COM a metody členů, které patří k těmto rozhraním. Vaše aplikace vytváří instance komponent MODELU COM, načítá z nich rozhraní a volá metody na těchto rozhraních, aby mohly těžit z funkcí implementovaných v komponentách modelu COM.

V praxi to vypadá podobně jako volání metod u běžného objektu C++. Ale existují určité rozdíly.

  • Objekt COM vynucuje přísnější zapouzdření než objekt C++. Objekt nelze vytvořit a pak volat jakoukoli veřejnou metodu. Místo toho jsou veřejné metody komponenty COM seskupeny do jednoho nebo více rozhraní COM. Chcete-li volat metodu, vytvoříte objekt a načtete z objektu rozhraní, které implementuje metodu. Rozhraní obvykle implementuje související sadu metod, které poskytují přístup ke konkrétní funkci objektu. Například rozhraní ID3D12Device představuje virtuální grafický adaptér a obsahuje metody, které umožňují vytvářet prostředky, například a mnoho dalších úloh souvisejících s adaptéry.
  • Objekt COM není vytvořen stejným způsobem jako objekt C++. Existuje několik způsobů, jak vytvořit objekt COM, ale všechny zahrnují techniky specifické pro com. Rozhraní DirectX API obsahuje celou řadu pomocných funkcí a metod, které zjednodušují vytváření většiny objektů COM rozhraní DirectX.
  • K řízení životnosti objektu COM je nutné použít techniky specifické pro com.
  • Server COM (obvykle knihovna DLL) nemusí být explicitně načten. Ani není možné propojit se statickou knihovnou, aby bylo možné použít komponentu COM. Každá komponenta COM má jedinečný registrovaný identifikátor (globálně jedinečný identifikátor nebo identifikátor GUID), který vaše aplikace používá k identifikaci objektu COM. Vaše aplikace identifikuje komponentu a COM runtime automaticky načte správnou knihovnu DLL serveru COM.
  • COM je binární specifikace. Objekty COM lze psát v různých jazycích a přistupovat k němu z různých jazyků. O zdrojovém kódu objektu nemusíte nic vědět. Například aplikace jazyka Visual Basic rutinně používají objekty COM napsané v jazyce C++.

Komponenta, objekt a rozhraní

Je důležité pochopit rozdíl mezi komponentami, objekty a rozhraními. Při neformálním použití můžete slyšet komponentu nebo objekt, na který odkazuje název jeho hlavního rozhraní. Ale termíny nejsou zaměnitelné. Komponenta může implementovat libovolný počet rozhraní; a objekt je instance komponenty. Zatímco všechny komponenty musí například implementovat IUnknown rozhraní, obvykle implementují alespoň jedno další rozhraní a mohou implementovat mnoho.

Chcete-li použít konkrétní metodu rozhraní, musíte nejen vytvořit instanci objektu, musíte také získat správné rozhraní z něj.

Kromě toho může stejné rozhraní implementovat více než jedna komponenta. Rozhraní je skupina metod, které provádějí logicky související sadu operací. Definice rozhraní určuje pouze syntaxi metod a jejich obecné funkce. Jakákoli komponenta MODELU COM, která potřebuje podporovat konkrétní sadu operací, to může provést implementací vhodného rozhraní. Některá rozhraní jsou vysoce specializovaná a implementují se pouze jednou komponentou; jiné jsou užitečné v různých situacích a jsou implementovány mnoha komponentami.

Pokud komponenta implementuje rozhraní, musí podporovat všechny metody v definici rozhraní. Jinými slovy, musíte být schopni volat jakoukoli metodu a mít jistotu, že existuje. Podrobnosti o způsobu implementace konkrétní metody se však mohou lišit od jedné komponenty po jinou. Různé komponenty mohou například použít různé algoritmy k přijetí konečného výsledku. Neexistuje ani žádná záruka, že metoda bude podporována netriviálním způsobem. Někdy komponenta implementuje běžně používané rozhraní, ale musí podporovat pouze podmnožinu metod. Zbývající metody budete moci úspěšně volat, ale vrátí HRESULT (což je standardní typ COM představující výsledkový kód) obsahující hodnotu E_NOTIMPL. Měli byste se podívat na jeho dokumentaci a podívat se, jak je rozhraní implementované libovolnou konkrétní komponentou.

Standard COM vyžaduje, aby se definice rozhraní po publikování nezměnila. Autor nemůže například přidat novou metodu do existujícího rozhraní. Autor musí místo toho vytvořit nové rozhraní. I když neexistují žádná omezení, jaké metody musí být v daném rozhraní, běžným postupem je mít rozhraní nové generace zahrnovat všechny metody starého rozhraní a všechny nové metody.

Není neobvyklé, že rozhraní má několik generací. Obvykle všechny generace provádějí v podstatě stejný celkový úkol, ale liší se v konkrétních specifikách. COM komponenta často implementuje každou aktuální a předchozí generaci vývojové linie daného rozhraní. To umožňuje starším aplikacím pokračovat v používání starších rozhraní objektu, zatímco novější aplikace mohou využívat funkce novějších rozhraní. Obvykle má sestupná skupina rozhraní stejný název a celé číslo, které označuje generování. Pokud by například původní rozhraní bylo pojmenováno IMyInterface (což znamená generace 1), pak by se další dvě generace nazývá IMyInterface2 a IMyInterface3. V případě rozhraní DirectX jsou následující generace obvykle pojmenovány podle čísla verze DirectX.

Guid

Identifikátory GUID jsou klíčovou součástí programovacího modelu COM. V nejzásadnějším případě je identifikátor GUID 128bitovou strukturou. Identifikátory GUID se však vytvářejí takovým způsobem, aby se zajistilo, že žádné dva identifikátory GUID nejsou stejné. Com používá identifikátory GUID pro dva primární účely.

  • Pokud chcete jedinečně identifikovat konkrétní komponentu COM. Identifikátor GUID, který je přiřazen k identifikaci komponenty COM, se nazývá identifikátor třídy (CLSID) a použijete CLSID, pokud chcete vytvořit instanci přidružené komponenty COM.
  • K jedinečné identifikaci konkrétního rozhraní COM. Identifikátor GUID, který je přiřazen k identifikaci rozhraní modelu COM, se nazývá identifikátor rozhraní (IID) a IID použijete při vyžádání konkrétního rozhraní z instance komponenty (objektu). IID rozhraní bude stejné bez ohledu na to, která komponenta implementuje rozhraní.

Pro usnadnění používání dokumentace DirectX obvykle odkazuje na komponenty a rozhraní podle jejich popisných názvů (například ID3D12Device) místo identifikátorů GUID. V kontextu dokumentace k Rozhraní DirectX neexistuje nejednoznačnost. Je technicky možné, aby třetí strana vytvořila rozhraní s popisným názvem ID3D12Device (aby bylo platné, potřebovalo by mít jiný identifikátor IID). V zájmu srozumitelnosti to ale nedoporučujeme.

Jediný jednoznačný způsob, jak odkazovat na konkrétní objekt nebo rozhraní, je tedy jeho identifikátor GUID.

I když je identifikátor GUID strukturou, identifikátor GUID se často vyjadřuje v ekvivalentní řetězcové podobě. Obecný formát řetězcové formy identifikátoru GUID je 32 šestnáctkových číslic ve formátu 8-4-4-4-12. To znamená, že {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}, kde každé x odpovídá šestnáctkové číslici. Například řetězcová forma IID pro rozhraní ID3D12Device je {189819F1-1DB6-4B57-BE54-1821339B85F7}.

Vzhledem k tomu, že skutečný identifikátor GUID je poněkud neohrabaný a snadno se dá chybně zadat, je obvykle k dispozici jeho ekvivalentní název. V kódu můžete tento název použít místo skutečné struktury při volání funkcí, například když předáte argument pro riid parametr D3D12CreateDevice. Zvyklostní pojmenovací konvence je použít předponu buď IID_ nebo CLSID_ k popisnému názvu rozhraní nebo objektu. Například IID rozhraní ID3D12Device se jmenuje IID_ID3D12Device.

Poznámka

Aplikace DirectX by měly být propojeny s dxguid.lib a uuid.lib, aby poskytovaly definice různých identifikátorů GUID rozhraní a tříd. Visual C++ a další kompilátory podporují rozšíření jazyka __uuidof operátoru, ale explicitní propojení stylu jazyka C s těmito knihovnami odkazů je také podporováno a plně přenositelné.

Hodnoty HRESULT

Většina metod COM vrací 32bitové celé číslo nazývané HRESULT. U většiny metod je HRESULT v podstatě struktura, která obsahuje dva primární informace.

  • Bez ohledu na to, jestli byla metoda úspěšná nebo neúspěšná.
  • Podrobnější informace o výsledku operace prováděné metodou.

Některé metody vrací hodnotu HRESULT ze standardní sady definované v Winerror.h. Metoda však může vrátit vlastní hodnotu HRESULT s více specializovanými informacemi. Tyto hodnoty jsou obvykle zdokumentované na referenční stránce metody.

Seznam HRESULT hodnot, které najdete na referenční stránce metody, je často pouze podmnožinou možných hodnot, které mohou být vráceny. Seznam obvykle pokrývá pouze hodnoty specifické pro metodu, stejně jako standardní hodnoty, které mají určitý význam pro konkrétní metodu. Měli byste předpokládat, že metoda může vrátit řadu standardních hodnot HRESULT, i když nejsou explicitně zdokumentované.

I když se hodnoty HRESULT často používají k vrácení informací o chybách, neměli byste je považovat za kódy chyb. Skutečnost, že bit, který označuje úspěch nebo selhání, je uložen odděleně od bitů, které obsahují podrobné informace, umožňuje HRESULT hodnoty mít libovolný počet kódů úspěchu a selhání. Podle konvence jsou názvy kódů úspěchu předponou S_ a kódy selhání E_. Například dva nejčastěji používané kódy jsou S_OK a E_FAIL, které označují jednoduchý úspěch nebo selhání.

Skutečnost, že metody COM mohou vrátit různé kódy úspěchu nebo selhání, znamená, že musíte být opatrní, jak testujete hodnotu HRESULT. Představte si například hypotetickou metodu s zdokumentovanými vrácenými hodnotami S_OK v případě úspěchu a E_FAIL, pokud ne. Mějte však na paměti, že metoda může také vrátit jiné kódy selhání nebo úspěchu. Následující fragment kódu ilustruje nebezpečí použití jednoduchého testu, kde hr obsahuje hodnotu HRESULT vrácenou metodou.

if (hr == E_FAIL)
{
    // Handle the failure case.
}
else
{
    // Handle the success case.
}  

Pokud v případě selhání tato metoda vrátí pouze E_FAIL (a ne nějaký jiný kód selhání), pak tento test funguje. Je ale realističtější, že daná metoda se implementuje tak, aby vrátila sadu konkrétních kódů selhání, například E_NOTIMPL nebo E_INVALIDARG. U výše uvedeného kódu by se tyto hodnoty nesprávně interpretovaly jako úspěch.

Pokud potřebujete podrobné informace o výsledku volání metody, musíte otestovat každou relevantní hodnotu HRESULT. Může vás však zajímat pouze to, jestli byla metoda úspěšná nebo neúspěšná. Robustní způsob, jak otestovat, jestli hodnota HRESULT značí úspěch nebo neúspěch, je předat hodnotu jednomu z následujících maker definovaných v winerror.h.

  • Makro SUCCEEDED vrátí hodnotu TRUE pro kód úspěchu a NEPRAVDA pro kód selhání.
  • Makro FAILED vrátí hodnotu TRUE pro kód selhání a NEPRAVDA pro kód úspěchu.

Předchozí fragment kódu tedy můžete opravit pomocí FAILED makra, jak je znázorněno v následujícím kódu.

if (FAILED(hr))
{
    // Handle the failure case.
}
else
{
    // Handle the success case.
}  

Tento opravený fragment kódu správně zpracovává E_NOTIMPL a E_INVALIDARG jako selhání.

I když většina metod COM vrací strukturované hodnoty HRESULT, malý počet používá hodnotu HRESULT k vrácení jednoduchého celého čísla. Implicitně jsou tyto metody vždy úspěšné. Pokud předáte HRESULT tohoto druhu do makra SUCCEEDED, vrátí makro vždy hodnotu TRUE. Příkladem běžně označované metody, která nevrací HRESULT je metoda IUnknown::Release, která vrátí ULONG. Tato metoda sníží počet odkazů objektu o jeden a vrátí aktuální počet odkazů. Informace o počítání odkazů najdete v tématu Správa životnosti objektu COM.

Adresa ukazatele

Pokud si prohlížíte několik referenčních stránek metod MODELU COM, pravděpodobně narazíte na něco podobného jako na následujícím obrázku.

HRESULT D3D12CreateDevice(
  IUnknown          *pAdapter,
  D3D_FEATURE_LEVEL MinimumFeatureLevel,
  REFIID            riid,
  void              **ppDevice
);

I když je normální ukazatel poměrně známý pro každého vývojáře C/C++, COM často používá další úroveň nepřímého směrování. Tato druhá úroveň indirectionu je označena dvěma hvězdičkami, **, za deklaraci typu a název proměnné má obvykle předponu pp. U výše uvedené funkce se parametr ppDevice obvykle označuje jako adresa ukazatele na void. V praxi je v tomto příkladu ppDevice adresa ukazatele na rozhraní ID3D12Device.

Na rozdíl od objektu C++ nemáte přímý přístup k metodám objektu COM. Místo toho je nutné získat ukazatel na rozhraní, které zveřejňuje metodu. K vyvolání metody použijete v podstatě stejnou syntaxi, jakou byste použili k vyvolání ukazatele na metodu C++. Pokud chcete například volat metodu IMyInterface::DoSomething, použijte následující syntaxi.

IMyInterface * pMyIface = nullptr;
...
pMyIface->DoSomething(...);

Potřeba druhé úrovně indirectionu pochází ze skutečnosti, že nevytvoříte ukazatele rozhraní přímo. Je nutné volat jednu z různých metod, jako je D3D12CreateDevice metoda uvedená výše. Chcete-li takovou metodu použít k získání ukazatele rozhraní, deklarujete proměnnou jako ukazatel na požadované rozhraní a pak předáte adresu této proměnné metodě. Jinými slovy předáte metodě adresu ukazatele. Po návratu metody proměnná odkazuje na požadované rozhraní a pomocí tohoto ukazatele můžete volat libovolnou metodu rozhraní.

IDXGIAdapter * pIDXGIAdapter = nullptr;
...
ID3D12Device * pD3D12Device = nullptr;
HRESULT hr = ::D3D12CreateDevice(
    pIDXGIAdapter,
    D3D_FEATURE_LEVEL_11_0,
    IID_ID3D12Device,
    &pD3D12Device);
if (FAILED(hr)) return E_FAIL;

// Now use pD3D12Device in the form pD3D12Device->MethodName(...);

Vytvoření objektu COM

Existuje několik způsobů, jak vytvořit objekt COM. Jedná se o dva nejčastěji používané v programování directX.

  • Nepřímo voláním metody nebo funkce DirectX, která za vás objekt vytvoří. Metoda vytvoří objekt a vrátí rozhraní objektu. Když vytvoříte objekt tímto způsobem, můžete někdy určit, které rozhraní se má vrátit, jindy je odvozeno rozhraní. Výše uvedený příklad kódu ukazuje, jak nepřímo vytvořit objekt COM zařízení Direct3D 12.
  • Přímo předáním CLSID objektu do funkce CoCreateInstance. Funkce vytvoří instanci objektu a vrátí ukazatel na zadané rozhraní.

Jednou, než vytvoříte jakékoli objekty COM, musíte inicializovat COM voláním CoInitializeEx funkce. Pokud vytváříte objekty nepřímo, metoda vytváření objektu zpracovává tuto úlohu. Pokud ale potřebujete vytvořit objekt s CoCreateInstance, musíte volat CoInitializeEx explicitně. Po dokončení musí být com neinicializován voláním CoUninitialize. Pokud zavoláte CoInitializeEx, musíte ho shodovat s voláním CoUninitialize. Aplikace, které potřebují explicitně inicializovat COM, to obvykle dělají ve své spouštěcí rutině a deinicializují COM ve své čisticí rutině.

Chcete-li vytvořit novou instanci objektu COM s CoCreateInstance, musíte mít CLSID objektu. Pokud je tento IDENTIFIKÁTOR CLSID veřejně dostupný, najdete ho v referenční dokumentaci nebo v příslušném souboru hlavičky. Pokud CLSID není veřejně dostupný, nemůžete objekt vytvořit přímo.

Funkce CoCreateInstance má pět parametrů. Pro objekty COM, které budete používat s DirectX, můžete normálně nastavit parametry následujícím způsobem.

rclsid Nastavit na CLSID objektu, který chcete vytvořit.

pUnkOuter Nastavit na nullptr. Tento parametr se používá pouze v případě, že agregujete objekty. Diskuze o agregaci modelu COM je mimo rozsah tohoto tématu.

dwClsContext Nastavte na CLSCTX_INPROC_SERVER. Toto nastavení označuje, že objekt je implementován jako knihovna DLL a spouští se jako součást procesu vaší aplikace.

riid Nastavte na IID rozhraní, které si přejete mít vráceno. Funkce vytvoří objekt a vrátí požadovaný ukazatel rozhraní v parametru ppv.

ppv Tuto hodnotu nastavte na adresu ukazatele, který bude nastaven na rozhraní určené riid při vrácení funkce. Tato proměnná by měla být deklarována jako ukazatel na požadované rozhraní a odkaz na ukazatel v seznamu parametrů by měl být přetypován jako (LPVOID *).

Vytvoření objektu nepřímo je obvykle mnohem jednodušší, jak jsme viděli v příkladu kódu výše. Předáte metodu vytvoření objektu adresu ukazatele rozhraní a metoda pak vytvoří objekt a vrátí ukazatel rozhraní. Když vytváříte objekt nepřímo, i když nemůžete zvolit, které rozhraní metoda vrací, často můžete zadat řadu věcí o tom, jak se má objekt vytvořit.

Můžete například předat D3D12CreateDevice hodnotu určující minimální úroveň funkce D3D, kterou má vrácené zařízení podporovat, jak je znázorněno v příkladu kódu výše.

Použití rozhraní COM

Při vytváření objektu COM vrátí metoda vytvoření ukazatel rozhraní. Tento ukazatel pak můžete použít pro přístup k některé z metod rozhraní. Syntaxe je stejná jako ta, která se používá s ukazatelem na metodu C++.

Vyžádání dalších rozhraní

V mnoha případech může být ukazatel rozhraní, který obdržíte z metody vytvoření, jediný, který potřebujete. Ve skutečnosti je poměrně běžné, že objekt exportuje pouze jedno rozhraní jiné než IUnknown. Mnoho objektů však exportuje více rozhraní a možná budete potřebovat ukazatele na několik z nich. Pokud potřebujete více rozhraní než to, které je vráceno metodou pro vytvoření, není nutné vytvářet nový objekt. Místo toho požádejte o další ukazatel rozhraní pomocí IUnknown::QueryInterface metoda.

Pokud vytvoříte objekt pomocí CoCreateInstance, můžete požádat o ukazatel rozhraní IUnknown a poté zavolat IUnknown::QueryInterface, abyste požádali o každé rozhraní, které potřebujete. Tento přístup je však neskonvenentní, pokud potřebujete jenom jedno rozhraní, a to vůbec nefunguje, pokud používáte metodu vytvoření objektu, která neumožňuje určit, který ukazatel rozhraní by se měl vrátit. V praxi obvykle nemusíte získat explicitní ukazatel na IUnknown, protože všechna rozhraní COM rozšiřují rozhraní IUnknown.

Rozšíření rozhraní je koncepčně podobné dědění z třídy C++. Podřízené rozhraní uvolňuje všechny metody nadřazeného rozhraní a navíc jednu nebo více vlastních metod. Ve skutečnosti se místo "extends" často používá "dědí z". Je třeba si uvědomit, že dědičnost je interní pro objekt. Vaše aplikace nemůže dědit z rozhraní objektu ani ho rozšířit. Podřízené rozhraní však můžete použít k volání libovolné metody podřízeného nebo nadřazeného objektu.

Vzhledem k tomu, že všechna rozhraní jsou podřízená IUnknown, můžete zavolat metodu QueryInterface na libovolném ukazateli rozhraní, které již pro objekt máte. Když to uděláte, musíte zadat IID rozhraní, které požadujete, a adresu ukazatele, který bude obsahovat ukazatel rozhraní při vrácení metody.

Například následující fragment kódu volá metodu IDXGIFactory2::CreateSwapChainForHwnd, aby vytvořila primární objekt swap řetězce. Tento objekt zveřejňuje několik rozhraní. Metoda CreateSwapChainForHwnd vrátí rozhraní IDXGISwapChain1. Následující kód pak použije rozhraní IDXGISwapChain1, aby zavolal QueryInterface a vyžádal si rozhraní IDXGISwapChain3.

HRESULT hr = S_OK;

IDXGISwapChain1 * pDXGISwapChain1 = nullptr;
hr = pIDXGIFactory->CreateSwapChainForHwnd(
    pCommandQueue, // For D3D12, this is a pointer to a direct command queue.
    hWnd,
    &swapChainDesc,
    nullptr,
    nullptr,
    &pDXGISwapChain1));
if (FAILED(hr)) return hr;

IDXGISwapChain3 * pDXGISwapChain3 = nullptr;
hr = pDXGISwapChain1->QueryInterface(IID_IDXGISwapChain3, (LPVOID*)&pDXGISwapChain3);
if (FAILED(hr)) return hr;

Poznámka

V jazyce C++ můžete použít makro IID_PPV_ARGS místo explicitního IID a přetypování ukazatele: pDXGISwapChain1->QueryInterface(IID_PPV_ARGS(&pDXGISwapChain3));. Často se to používá pro metody vytváření stejně jako QueryInterface. Další informace najdete v combaseapi.h.

Správa životnosti objektu COM

Při vytvoření objektu systém přidělí potřebné paměťové prostředky. Pokud už objekt nepotřebujete, měl by být zničen. Systém může tuto paměť použít pro jiné účely. U objektů jazyka C++ můžete řídit životnost objektu přímo pomocí operátorů new a delete v případech, kdy pracujete na této úrovni, nebo pomocí doby života zásobníku a oboru. Com neumožňuje přímo vytvářet nebo zničit objekty. Důvodem tohoto návrhu je, že stejný objekt může používat více než jedna část aplikace nebo v některých případech více než jedna aplikace. Pokud by jeden z těchto odkazů objekt zničil, ostatní odkazy by byly neplatné. Místo toho COM využívá systém počítání referencí k řízení životnosti objektu.

Počet referencí objektu představuje počet, kolikrát byly jeho rozhraní požadovány. Pokaždé, když se požaduje rozhraní, se zvýší počet odkazů. Aplikace uvolní rozhraní, pokud už toto rozhraní není potřeba, a sníží se počet odkazů. Pokud je počet odkazů větší než nula, zůstane objekt v paměti. Když počet odkazů dosáhne nuly, objekt se zničí sám. O počtu odkazů objektu nemusíte nic vědět. Pokud získáte a uvolníte rozhraní objektu správně, bude mít objekt odpovídající životnost.

Správné zpracování počítání odkazů je klíčovou součástí programování modelu COM. Pokud to neuděláte, může snadno dojít k úniku paměti nebo k pádu aplikace. Jednou z nejběžnějších chyb, které dělají programátoři modelu COM, je nepropustit rozhraní. V takovém případě počet odkazů nikdy nedosáhne nuly a objekt zůstane v paměti po neomezenou dobu.

Poznámka

Direct3D 10 nebo novější má mírně upravená pravidla životnosti objektů. Konkrétně objekty odvozené z ID3DxxDeviceChild nikdy nedosáhly nadřazeného zařízení (to znamená, že pokud vlastnící ID3DxxDevice dosáhne 0 refcount, všechny podřízené objekty jsou okamžitě neplatné). Pokud také použijete Nastavit metody pro vazbu objektů s kanálem vykreslení, tyto odkazy nezvyšují počet odkazů (to znamená, že jsou slabé odkazy). V praxi to nejlépe zvládnete tím, že nejprve zcela uvolníte všechny podřízené objekty zařízení, než uvolníte samotné zařízení.

Zvýšení a snížení počtu odkazů

Kdykoli získáte nový ukazatel rozhraní, musí se referenční počet zvýšit voláním IUnknown::AddRef. Aplikace ale obvykle tuto metodu nemusí volat. Pokud získáte ukazatel rozhraní voláním metody vytvoření objektu, nebo voláním IUnknown::QueryInterface, pak objekt automaticky zvýší počet odkazů. Pokud však vytvoříte ukazatel rozhraní jiným způsobem, například zkopírováním existujícího ukazatele, musíte explicitně volat IUnknown::AddRef. Jinak, když uvolníte původní ukazatel rozhraní, může být objekt zničen, i když stále budete muset použít kopii ukazatele.

Musíte uvolnit všechny ukazatele rozhraní, bez ohledu na to, zda jste vy nebo objekt zvýšili počet referencí. Pokud už ukazatel rozhraní nepotřebujete, zavolejte IUnknown::Release, čímž dekrementujete počet odkazů. Běžným postupem je inicializace všech ukazatelů rozhraní na nullptra jejich následné nastavení zpět na nullptr při jejich vydání. Tato konvence umožňuje otestovat všechny ukazatele rozhraní v kódu čištění. Ty, které nejsou nullptr jsou stále aktivní, a před ukončením aplikace je musíte uvolnit.

Následující fragment kódu rozšiřuje ukázku uvedenou dříve, aby ilustroval, jak zpracovat počítání odkazů.

HRESULT hr = S_OK;

IDXGISwapChain1 * pDXGISwapChain1 = nullptr;
hr = pIDXGIFactory->CreateSwapChainForHwnd(
    pCommandQueue, // For D3D12, this is a pointer to a direct command queue.
    hWnd,
    &swapChainDesc,
    nullptr,
    nullptr,
    &pDXGISwapChain1));
if (FAILED(hr)) return hr;

IDXGISwapChain3 * pDXGISwapChain3 = nullptr;
hr = pDXGISwapChain1->QueryInterface(IID_IDXGISwapChain3, (LPVOID*)&pDXGISwapChain3);
if (FAILED(hr)) return hr;

IDXGISwapChain3 * pDXGISwapChain3Copy = nullptr;

// Make a copy of the IDXGISwapChain3 interface pointer.
// Call AddRef to increment the reference count and to ensure that
// the object is not destroyed prematurely.
pDXGISwapChain3Copy = pDXGISwapChain3;
pDXGISwapChain3Copy->AddRef();
...
// Cleanup code. Check to see whether the pointers are still active.
// If they are, then call Release to release the interface.
if (pDXGISwapChain1 != nullptr)
{
    pDXGISwapChain1->Release();
    pDXGISwapChain1 = nullptr;
}
if (pDXGISwapChain3 != nullptr)
{
    pDXGISwapChain3->Release();
    pDXGISwapChain3 = nullptr;
}
if (pDXGISwapChain3Copy != nullptr)
{
    pDXGISwapChain3Copy->Release();
    pDXGISwapChain3Copy = nullptr;
}

Inteligentní ukazatele COM

Zatím kód explicitně volal Release a AddRef pro zachování počtu odkazů pomocí metod IUnknown. Tento vzorec vyžaduje, aby se programátoři pečlivě pamatovali na správné zachování počtu ve všech možných cestách kódu. To může vést ke složitému zpracování chyb a s povoleným zpracováním výjimek jazyka C++ může být obzvláště obtížné implementovat. Lepším řešením jazyka C++ je použití inteligentního ukazatele.

  • winrt::com_ptr je inteligentní ukazatel poskytovaný projekcemi jazyka C++/WinRT . Toto je doporučený inteligentní ukazatel modelu COM pro použití v aplikacích pro UWP. Mějte na paměti, že C++/WinRT vyžaduje C++17.

  • Microsoft::WRL::ComPtr je inteligentní ukazatel poskytovaný knihovnou šablon jazyka CRL (Windows Runtime C++ Template Library). Tato knihovna je "čistě" C++, takže ji lze využít pro aplikace prostředí Windows Runtime (prostřednictvím C++/CX nebo C++/WinRT) a desktopových aplikací Win32. Tento inteligentní ukazatel funguje také ve starších verzích Windows, které nepodporují rozhraní API prostředí Windows Runtime. U desktopových aplikací Win32 můžete použít #include <wrl/client.h> k zahrnutí pouze této třídy a volitelně také definovat symbol preprocesoru __WRL_CLASSIC_COM_STRICT__. Další informace najdete v části rekapitulace inteligentních ukazatelů COM.

  • CComPtr je inteligentní ukazatel poskytovaný knihovnou Active Template Library (ATL) . Microsoft::WRL::ComPtr je novější verze této implementace, která řeší řadu drobných problémů s používáním, takže použití tohoto inteligentního ukazatele se nedoporučuje pro nové projekty. Další informace naleznete v tématu Jak vytvořit a používat CComPtr a CComQIPtr.

Použití ATL s DirectX 9

Chcete-li použít knihovnu ATL (Active Template Library) s Rozhraním DirectX 9, je nutné předefinovat rozhraní pro kompatibilitu knihovny ATL. To vám umožní správně použít třídu CComQIPtr k získání ukazatele na rozhraní.

Zjistíte, pokud rozhraní pro ATL nepředefinujete, protože se zobrazí následující chybová zpráva.

[...]\atlmfc\include\atlbase.h(4704) :   error C2787: 'IDirectXFileData' : no GUID has been associated with this object

Následující ukázka kódu ukazuje, jak definovat rozhraní IDirectXFileData.

// Explicit declaration
struct __declspec(uuid("{3D82AB44-62DA-11CF-AB39-0020AF71E433}")) IDirectXFileData;

// Macro method
#define RT_IID(iid_, name_) struct __declspec(uuid(iid_)) name_
RT_IID("{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}", IDirectXFileData);

Po opětovném definování rozhraní je nutné použít metodu Attach k připojení rozhraní k ukazateli na rozhraní vráceného ::Direct3DCreate9. Pokud ne, rozhraní IDirect3D9 nebude správně uvolněno třídou inteligentního ukazatele.

CComPtr třída interně volá IUnknown::AddRef na ukazatel rozhraní při vytvoření objektu a při přiřazení rozhraní ke CComPtr třídě. Chcete-li zabránit úniku ukazatele rozhraní, nevolejte **IUnknown::AddRef na rozhraní vrácené z ::Direct3DCreate9.

Následující kód správně uvolní rozhraní bez volání IUnknown::AddRef.

CComPtr<IDirect3D9> d3d;
d3d.Attach(::Direct3DCreate9(D3D_SDK_VERSION));

Použijte předchozí kód. Nepoužívejte následující kód, který volá IUnknown::AddRef následovaný IUnknown::Releasea neuvolní odkaz přidaný ::Direct3DCreate9.

CComPtr<IDirect3D9> d3d = ::Direct3DCreate9(D3D_SDK_VERSION);

Všimněte si, že toto je jediné místo v Direct3D 9, kde budete muset tímto způsobem použít metodu Attach.

Další informace o třídách CComPTR a CComQIPtr naleznete v definicích v souboru hlaviček Atlbase.h.