Delen via


DirectX programmeren met COM

Het Microsoft Component Object Model (COM) is een objectgeoriënteerd programmeermodel dat door verschillende technologieën wordt gebruikt, waaronder het grootste deel van het DirectX API-oppervlak. Daarom gebruikt u (als DirectX-ontwikkelaar) onvermijdelijk COM wanneer u DirectX programmeert.

Notitie

Het onderwerp COM-onderdelen gebruiken met C++/WinRT- laat zien hoe u DirectX-API's (en eventuele COM-API's) gebruikt met behulp van C++/WinRT-. Dat is verreweg de handigste en aanbevolen technologie die moet worden gebruikt.

U kunt ook onbewerkte COM gebruiken en dat is waar dit onderwerp over gaat. U hebt een basiskennis nodig van de principes en programmeertechnieken die betrokken zijn bij het gebruik van COM API's. Hoewel COM een reputatie heeft om moeilijk en complex te zijn, is de COM-programmering die vereist is voor de meeste DirectX-toepassingen eenvoudig. Dit komt deels doordat u de COM-objecten gebruikt die worden geleverd door DirectX. Het is niet nodig om uw eigen COM-objecten te maken, wat meestal de oorzaak is van de complexiteit.

Overzicht van COM-onderdelen

Een COM-object is in wezen een ingekapseld onderdeel van de functionaliteit die door toepassingen kan worden gebruikt om een of meer taken uit te voeren. Voor de implementatie worden een of meer COM-onderdelen verpakt in een binair bestand dat een COM-server wordt genoemd; vaker dan geen DLL.

Een traditionele DLL exporteert gratis functies. Een COM-server kan hetzelfde doen. Maar de COM-onderdelen in de COM-server maken COM-interfaces en lidmethoden beschikbaar die behoren tot deze interfaces. Uw toepassing maakt exemplaren van COM-onderdelen, haalt er interfaces van op en roept methoden op die interfaces aan om te profiteren van de functies die zijn geïmplementeerd in de COM-onderdelen.

In de praktijk lijkt dit op het aanroepen van methoden op een normaal C++-object. Maar er zijn enkele verschillen.

  • Een COM-object dwingt strengere inkapseling af dan een C++-object wel doet. U kunt het object niet alleen maken en vervolgens een openbare methode aanroepen. In plaats daarvan worden de openbare methoden van een COM-onderdeel gegroepeerd in een of meer COM-interfaces. Als u een methode wilt aanroepen, maakt u het object aan en haalt u uit het object de interface op die de methode implementeert. Een interface implementeert doorgaans een gerelateerde set methoden die toegang bieden tot een bepaalde functie van het object. De ID3D12Device-interface vertegenwoordigt bijvoorbeeld een virtuele grafische adapter en bevat methoden waarmee u resources kunt maken, bijvoorbeeld en veel andere adaptergerelateerde taken.
  • Een COM-object wordt niet op dezelfde manier gemaakt als een C++-object. Er zijn verschillende manieren om een COM-object te maken, maar hiervoor zijn allemaal COM-specifieke technieken nodig. De DirectX-API bevat diverse helperfuncties en -methoden die het maken van de meeste DirectX COM-objecten vereenvoudigen.
  • U moet COM-specifieke technieken gebruiken om de levensduur van een COM-object te bepalen.
  • De COM-server (meestal een DLL) hoeft niet expliciet te worden geladen. U kunt ook geen koppeling maken naar een statische bibliotheek om een COM-onderdeel te kunnen gebruiken. Elk COM-onderdeel heeft een unieke geregistreerde id (een globally-unique identifier of GUID), die uw toepassing gebruikt om het COM-object te identificeren. Uw toepassing identificeert het onderdeel en de COM-runtime laadt automatisch de juiste DLL van de COM-server.
  • COM is een binaire specificatie. COM-objecten kunnen in verschillende talen worden geschreven en geopend. U hoeft niets te weten over de broncode van het object. Visual Basic-toepassingen maken bijvoorbeeld regelmatig gebruik van COM-objecten die zijn geschreven in C++.

Onderdeel, object en interface

Het is belangrijk om inzicht te hebben in het onderscheid tussen onderdelen, objecten en interfaces. In informeel gebruik hoort u mogelijk een onderdeel of object waarmee wordt verwezen naar de naam van de hoofdinterface. Maar de termen zijn niet uitwisselbaar. Een onderdeel kan een willekeurig aantal interfaces implementeren; en een object is een exemplaar van een onderdeel. Hoewel alle onderdelen bijvoorbeeld de IUnknown-interfacemoeten implementeren, implementeren ze normaal gesproken ten minste één extra interface en kunnen ze er veel implementeren.

Als u een bepaalde interfacemethode wilt gebruiken, moet u niet alleen een exemplaar van een object maken, maar moet u er ook de juiste interface van verkrijgen.

Daarnaast kan meer dan één onderdeel dezelfde interface implementeren. Een interface is een groep methoden die een logisch gerelateerde set bewerkingen uitvoeren. De interfacedefinitie geeft alleen de syntaxis van de methoden en hun algemene functionaliteit op. Elk COM-onderdeel dat een bepaalde set bewerkingen moet ondersteunen, kan dit doen door een geschikte interface te implementeren. Sommige interfaces zijn zeer gespecialiseerd en worden slechts door één onderdeel geïmplementeerd; andere zijn nuttig in verschillende omstandigheden en worden door veel onderdelen geïmplementeerd.

Als een onderdeel een interface implementeert, moet het elke methode in de interfacedefinitie ondersteunen. Met andere woorden, u moet elke methode kunnen aanroepen en erop vertrouwen dat deze bestaat. De details van hoe een bepaalde methode wordt geïmplementeerd, kunnen echter verschillen van het ene naar het andere onderdeel. Verschillende onderdelen kunnen bijvoorbeeld verschillende algoritmen gebruiken om het uiteindelijke resultaat te bereiken. Er is ook geen garantie dat een methode op een niet-triviale manier wordt ondersteund. Soms implementeert een onderdeel een veelgebruikte interface, maar moet het alleen een subset van de methoden ondersteunen. U kunt de resterende methoden nog steeds aanroepen, maar ze retourneren een HRESULT- (een standaard COM-type dat een resultaatcode vertegenwoordigt) die de waarde E_NOTIMPLbevat. Raadpleeg de bijbehorende documentatie om te zien hoe een interface wordt geïmplementeerd door een bepaald onderdeel.

De COM-standaard vereist dat een interfacedefinitie niet mag worden gewijzigd zodra deze is gepubliceerd. De auteur kan bijvoorbeeld geen nieuwe methode toevoegen aan een bestaande interface. De auteur moet in plaats daarvan een nieuwe interface maken. Hoewel er geen beperkingen zijn voor welke methoden in die interface moeten zijn, is het gebruikelijk dat de volgende generatie interface alle methoden van de oude interface bevat, plus eventuele nieuwe methoden.

Het is niet ongebruikelijk dat een interface meerdere generaties heeft. Normaal gesproken voeren alle generaties in wezen dezelfde algemene taak uit, maar ze verschillen in specifieke aspecten. Vaak implementeert een COM-onderdeel de huidige en alle eerdere generaties van de afstamming van een bepaalde interface. Hierdoor kunnen oudere toepassingen de oudere interfaces van het object blijven gebruiken, terwijl nieuwere toepassingen kunnen profiteren van de functies van de nieuwere interfaces. Normaal gesproken heeft een afstammingsgroep van interfaces allemaal dezelfde naam, plus een geheel getal dat de generatie aangeeft. Als de oorspronkelijke interface bijvoorbeeld de naam IMyInterface heeft, wat de 1e generatie impliceert, dan zouden de volgende twee generaties IMyInterface2 en IMyInterface3worden genoemd. In het geval van DirectX-interfaces worden opeenvolgende generaties doorgaans genoemd naar het versienummer van DirectX.

GUIDs

GUID's vormen een belangrijk onderdeel van het COM-programmeermodel. In de meest eenvoudige vorm is een GUID een 128-bits structuur. GUID's worden echter zodanig gemaakt dat er geen twee GUID's hetzelfde zijn. COM maakt intensief gebruik van GUID's voor twee primaire doeleinden.

  • Een bepaald COM-onderdeel uniek identificeren. Een GUID die is toegewezen om een COM-onderdeel te identificeren, wordt een klasse-id (CLSID) genoemd en u gebruikt een CLSID wanneer u een exemplaar van het bijbehorende COM-onderdeel wilt maken.
  • Om een bepaalde COM-interface uniek te identificeren. Een GUID die is toegewezen om een COM-interface te identificeren, wordt een interface-id (IID) genoemd en u gebruikt een IID wanneer u een bepaalde interface aanvraagt bij een exemplaar van een onderdeel (een object). De IID van een interface is hetzelfde, ongeacht welk onderdeel de interface implementeert.

Voor het gemak verwijst de DirectX-documentatie doorgaans naar onderdelen en interfaces op basis van hun beschrijvende namen (bijvoorbeeld ID3D12Device) in plaats van door hun GUID's. In de context van de DirectX-documentatie is er geen dubbelzinnigheid. Het is technisch gezien mogelijk dat een derde partij een interface met de beschrijvende naam ID3D12Device creëert (het zou een andere IID moeten hebben om geldig te zijn). In het belang van duidelijkheid raden we dat echter niet aan.

De enige ondubbelzinnige manier om naar een bepaald object of een bepaalde interface te verwijzen, is dus door de GUID.

Hoewel een GUID een structuur is, wordt een GUID vaak uitgedrukt in equivalente tekenreeksvorm. De algemene notatie van de tekenreeksvorm van een GUID is 32 hexadecimale cijfers, in de notatie 8-4-4-4-12. Dat wil gezegd: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxx}, waarbij elke x overeenkomt met een hexadecimaal cijfer. De tekenreeksvorm van de IID voor de interface ID3D12Device is bijvoorbeeld {189819F1-1DB6-4B57-BE54-1821339B85F7}.

Omdat de werkelijke GUID enigszins onhandig is om te gebruiken en gemakkelijk te mislezen, wordt meestal ook een equivalente naam opgegeven. In uw code kunt u deze naam gebruiken in plaats van de werkelijke structuur wanneer u functies aanroept, bijvoorbeeld wanneer u een argument voor de parameter riid doorgeeft aan D3D12CreateDevice-. De gebruikelijke naamgevingsconventie is om respectievelijk IID_ of CLSID_ toe te voegen aan de beschrijvende naam van de interface of het object. De naam van de IID van de ID3D12Device interface is bijvoorbeeld IID_ID3D12Device.

Notitie

DirectX-toepassingen moeten worden gekoppeld aan dxguid.lib en uuid.lib om definities te bieden voor de verschillende interface- en klasse-GUID's. Visual C++ en andere compilers ondersteunen de __uuidof operator-taaluitbreiding, maar expliciete C-stijlkoppeling met deze koppellibraries wordt ook ondersteund en is volledig draagbaar.

HRESULT-waarden

De meeste COM-methoden retourneren een 32-bits geheel getal dat een HRESULT-wordt genoemd. Bij de meeste methoden is HRESULT in feite een structuur die twee primaire gegevens bevat.

  • Of de methode is geslaagd of mislukt.
  • Meer gedetailleerde informatie over het resultaat van de bewerking die door de methode wordt uitgevoerd.

Sommige methoden retourneren een HRESULT- waarde uit de standaardset die is gedefinieerd in Winerror.h. Een methode is echter gratis om een aangepaste HRESULT- waarde te retourneren met meer gespecialiseerde informatie. Deze waarden worden normaal gesproken gedocumenteerd op de referentiepagina van de methode.

De lijst met HRESULT waarden die u op de referentiepagina van een methode vindt, is vaak slechts een subset van de mogelijke waarden die kunnen worden geretourneerd. De lijst bevat doorgaans alleen de waarden die specifiek zijn voor de methode, evenals die standaardwaarden met een bepaalde methodespecifieke betekenis. U moet ervan uitgaan dat een methode een verscheidenheid aan standaardwaarden HRESULT waarden kan retourneren, zelfs als deze niet expliciet worden gedocumenteerd.

Hoewel HRESULT- waarden vaak worden gebruikt om foutinformatie te retourneren, moet u deze niet beschouwen als foutcodes. Het feit dat de bit die aangeeft dat geslaagd of mislukt is, afzonderlijk wordt opgeslagen van de bits die de gedetailleerde informatie bevatten, stelt HRESULT waarden in staat om een willekeurig aantal succes- en foutcodes te hebben. Volgens de conventie worden de namen van succescodes voorafgegaan door S_ en foutcodes door E_. De twee meest gebruikte codes zijn bijvoorbeeld S_OK en E_FAIL, die respectievelijk een eenvoudig succes of falen aangeven.

Het feit dat COM-methoden verschillende succes- of foutcodes kunnen retourneren, betekent dat u voorzichtig moet zijn met het testen van de HRESULT waarde. Denk bijvoorbeeld aan een hypothetische methode met gedocumenteerde retourwaarden van S_OK als dat lukt en E_FAIL indien niet. Vergeet echter niet dat de methode ook andere fout- of succescodes kan retourneren. Het volgende codefragment illustreert het gevaar van het gebruik van een eenvoudige test, waarbij hr de HRESULT- waarde bevat die door de methode wordt geretourneerd.

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

Zolang deze methode in het geval van fouten alleen E_FAIL retourneert (en niet een andere foutcode), werkt deze test. Het is echter realistischer dat een bepaalde methode wordt geïmplementeerd om een set specifieke foutcodes te retourneren, mogelijk E_NOTIMPL of E_INVALIDARG. Met de bovenstaande code worden deze waarden onjuist geïnterpreteerd als een succes.

Als u gedetailleerde informatie nodig hebt over het resultaat van de methode-aanroep, moet u elke relevante HRESULT waarde testen. Mogelijk bent u echter alleen geïnteresseerd in of de methode is geslaagd of mislukt. Een robuuste manier om te testen of een HRESULT- waarde aangeeft dat de waarde is geslaagd of mislukt, is om de waarde door te geven aan een van de volgende macro's, gedefinieerd in Winerror.h.

  • De macro SUCCEEDED retourneert TRUE voor een geslaagde code en ONWAAR voor een foutcode.
  • De FAILED macro retourneert TRUE voor een foutcode en ONWAAR voor een geslaagde code.

U kunt het voorgaande codefragment dus herstellen met behulp van de macro FAILED, zoals wordt weergegeven in de volgende code.

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

Dit gecorrigeerde codefragment behandelt E_NOTIMPL en E_INVALIDARG correct als fouten.

Hoewel de meeste COM-methoden gestructureerde HRESULT- waarden retourneren, gebruikt een klein getal de HRESULT- om een eenvoudig geheel getal te retourneren. Impliciet zijn deze methoden altijd succesvol. Als u een HRESULT- van dit type doorgeeft aan de macro GESLAAGD, retourneert de macro altijd WAAR. Een voorbeeld van een veelgebruikte methode die geen HRESULT- retourneert, is de methode IUnknown::Release, die een ULONG retourneert. Met deze methode wordt het aantal verwijzingen van een object met één verminderd en wordt het huidige aantal verwijzingen geretourneerd. Zie Het beheren van de levensduur van een COM-object voor een bespreking van het tellen van verwijzingen.

Het adres van een aanwijzer

Als u enkele COM-methodereferentiepagina's bekijkt, zult u waarschijnlijk iets als het volgende tegenkomen.

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

Hoewel een normale aanwijzer vrij bekend is bij elke C/C++-ontwikkelaar, gebruikt COM vaak een extra niveau van indirectie. Dit tweede niveau van indirectie wordt aangegeven met twee sterretjes, **, na de typedeclaratie en de variabelenaam heeft doorgaans een voorvoegsel van pp. Voor de bovenstaande functie wordt de parameter ppDevice meestal het adres van een aanwijzer naar een ongeldige waarde genoemd. In dit voorbeeld is ppDevice het adres van een aanwijzer naar een ID3D12Device-interface.

In tegenstelling tot een C++-object hebt u geen rechtstreeks toegang tot de methoden van een COM-object. In plaats daarvan moet u een aanwijzer verkrijgen naar een interface die de methode beschikbaar maakt. Als u de methode wilt aanroepen, gebruikt u in feite dezelfde syntaxis als voor het aanroepen van een aanwijzer naar een C++-methode. Als u bijvoorbeeld de methode IMyInterface::D oSomething wilt aanroepen, gebruikt u de volgende syntaxis.

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

De noodzaak van een tweede indirectieniveau is het feit dat u geen interfacepointers rechtstreeks maakt. U moet een van de verschillende methoden aanroepen, zoals de D3D12CreateDevice methode die hierboven wordt weergegeven. Als u een dergelijke methode wilt gebruiken om een interfaceaanwijzer te verkrijgen, declareert u een variabele als een aanwijzer naar de gewenste interface en geeft u vervolgens het adres van die variabele door aan de methode. Met andere woorden, u geeft het adres van een aanwijzer door aan de methode. Wanneer de methode wordt geretourneerd, verwijst de variabele naar de aangevraagde interface en kunt u die aanwijzer gebruiken om een van de methoden van de interface aan te roepen.

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(...);

Een COM-object maken

Er zijn verschillende manieren om een COM-object te maken. Dit zijn de twee meest gebruikte in DirectX-programmering.

  • Indirect door een DirectX-methode of functie aan te roepen waarmee het object voor u wordt gemaakt. De methode maakt het object en retourneert een interface op het object. Wanneer u op deze manier een object maakt, kunt u soms opgeven welke interface moet worden geretourneerd, andere keren dat de interface wordt geïmpliceerd. In het bovenstaande codevoorbeeld ziet u hoe u indirect een COM-object voor Direct3D 12-apparaten maakt.
  • Rechtstreeks door de CLSID van het object door te geven aan de functie CoCreateInstance. De functie maakt een exemplaar van het object en retourneert een aanwijzer naar een interface die u opgeeft.

Voordat u COM-objecten maakt, moet u COM één keer initialiseren door de functie CoInitializeEx aan te roepen. Als u indirect objecten maakt, verwerkt de methode voor het maken van objecten deze taak. Maar als u een object wilt maken met CoCreateInstance, moet u CoInitializeEx expliciet aanroepen. Wanneer u klaar bent, moet COM worden geïnitialiseerd door CoUninitializeaan te roepen. Als u een oproep naar CoInitializeEx doet, moet u deze vergelijken met een aanroep om CoUninitialize. Doorgaans initialiseren toepassingen die COM expliciet moeten initialiseren, dit in hun opstartroutine, en deïnstantiëren ze COM in hun opschoonroutine.

Als u een nieuw exemplaar van een COM-object wilt maken met CoCreateInstance, moet u de CLSID van het object hebben. Als deze CLSID openbaar beschikbaar is, vindt u deze in de referentiedocumentatie of het juiste headerbestand. Als de CLSID niet openbaar beschikbaar is, kunt u het object niet rechtstreeks maken.

De functie CoCreateInstance heeft vijf parameters. Voor de COM-objecten die u gaat gebruiken met DirectX, kunt u de parameters normaal gesproken als volgt instellen.

rclsid Stel dit in op de CLSID van het object dat u wilt maken.

pUnkOuter- ingesteld op nullptr. Deze parameter wordt alleen gebruikt als u objecten samenvoegt. Een bespreking van COM-aggregatie valt buiten het bereik van dit onderwerp.

dwClsContext ingesteld op CLSCTX_INPROC_SERVER. Deze instelling geeft aan dat het object is geïmplementeerd als een DLL en wordt uitgevoerd als onderdeel van het proces van uw toepassing.

riid Ingesteld op de IID van de interface die u wilt retourneren. De functie maakt het object en retourneert de aangevraagde interfacepointer in de ppv-parameter.

ppv- Stel dit in op het adres van een pointer die wordt ingesteld op de interface die is opgegeven door riid wanneer de functie terugkeert. Deze variabele moet worden gedeclareerd als een aanwijzer naar de aangevraagde interface en de verwijzing naar de aanwijzer in de parameterlijst moet worden cast als (LPVOID *).

Het indirect maken van een object is meestal veel eenvoudiger, zoals we in het bovenstaande codevoorbeeld hebben gezien. U geeft de methode voor het maken van objecten door aan het adres van een interfaceaanwijzer en de methode maakt vervolgens het object en retourneert een interfaceaanwijzer. Wanneer u indirect een object maakt, zelfs als u niet kunt kiezen welke interface de methode retourneert, kunt u vaak nog steeds verschillende dingen opgeven over hoe het object moet worden gemaakt.

U kunt bijvoorbeeld doorgeven aan D3D12CreateDevice een waarde die het minimale D3D-functieniveau aangeeft dat het geretourneerde apparaat moet ondersteunen, zoals wordt weergegeven in het bovenstaande codevoorbeeld.

COM-interfaces gebruiken

Wanneer u een COM-object maakt, retourneert de methode een interface-aanwijzer. Vervolgens kunt u die aanwijzer gebruiken om toegang te krijgen tot een van de methoden van de interface. De syntaxis is identiek aan de syntaxis die wordt gebruikt met een aanwijzer naar een C++-methode.

Aanvullende interfaces aanvragen

In veel gevallen is de interfacepointer die u ontvangt van de aanmaakmethode mogelijk de enige die u nodig hebt. In feite is het relatief gebruikelijk dat een object slechts één andere interface exporteert dan IUnknown. Veel objecten exporteren echter meerdere interfaces en u hebt mogelijk aanwijzers nodig. Als u meer interfaces nodig hebt dan de interfaces die worden geretourneerd door de aanmaakmethode, hoeft u geen nieuw object te maken. Vraag in plaats daarvan een andere interfaceaanwijzer aan met behulp van de methode IUnknown::QueryInterface.

Als u uw object maakt met CoCreateInstance-, kunt u een interfaceaanwijzer voor IUnknown aanvragen en vervolgens IUnknown::QueryInterface aanroepen om elke interface aan te vragen die u nodig hebt. Deze benadering is echter onhandig als u slechts één interface nodig hebt en het werkt helemaal niet als u een methode voor het maken van objecten gebruikt waarmee u niet kunt opgeven welke interfaceaanwijzer moet worden geretourneerd. In de praktijk hoeft u meestal geen expliciete IUnknown- aanwijzer te verkrijgen, omdat alle COM-interfaces de IUnknown interface uitbreiden.

Het uitbreiden van een interface is conceptueel vergelijkbaar met het overnemen van een C++-klasse. De afgeleide interface bevat alle methoden van de bovenliggende interface, plus een of meer van zijn eigen methoden. In feite zal je vaak zien dat 'erft van' wordt gebruikt in plaats van 'breidt uit'. Wat u moet onthouden, is dat de overname intern is voor het object. Uw toepassing kan de interface van een object niet overnemen of uitbreiden. U kunt echter de interface van het kind gebruiken om een van de methoden van het kind of de ouder aan te roepen.

Omdat alle interfaces onderliggende elementen van IUnknown-zijn, kunt u QueryInterface aanroepen op een van de interfaceaanwijzers die u al voor het object hebt. Wanneer u dit doet, moet u de IID opgeven van de interface die u aanvraagt en het adres van een pointer die de interfacepointer zal bevatten wanneer de methode voltooid is.

Met het volgende codefragment wordt bijvoorbeeld IDXGIFactory2::CreateSwapChainForHwnd aangeroepen om een primair swap chain-object te maken. Dit object bevat verschillende interfaces. De methode CreateSwapChainForHwnd retourneert een IDXGISwapChain1-interface. De volgende code gebruikt vervolgens de IDXGISwapChain1 interface om QueryInterface- aan te roepen om een IDXGISwapChain3-interface aan te vragen.

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;

Notitie

In C++ kunt u de IID_PPV_ARGS macro gebruiken in plaats van de expliciete IID- en cast-aanwijzer: pDXGISwapChain1->QueryInterface(IID_PPV_ARGS(&pDXGISwapChain3));. Dit wordt vaak gebruikt voor zowel het creëren van methoden als QueryInterface. Zie combaseapi.h voor meer informatie.

De levensduur van een COM-object beheren

Wanneer een object wordt gemaakt, wijst het systeem de benodigde geheugenbronnen toe. Wanneer een object niet meer nodig is, moet het worden vernietigd. Het systeem kan dat geheugen gebruiken voor andere doeleinden. Met C++-objecten kunt u de levensduur van het object rechtstreeks beheren met de new- en delete operators in gevallen waarin u op dat niveau werkt, of alleen met behulp van de levensduur van de stack en het bereik. MET COM kunt u geen objecten rechtstreeks maken of vernietigen. De reden voor dit ontwerp is dat hetzelfde object kan worden gebruikt door meer dan één deel van uw toepassing of, in sommige gevallen, door meer dan één toepassing. Als een van deze verwijzingen het object zou vernietigen, zouden de andere verwijzingen ongeldig worden. In plaats daarvan gebruikt COM een referentietelling systeem om de levensduur van een object te bepalen.

Het aantal verwijzingen van een object is het aantal keren dat een van de interfaces is aangevraagd. Telkens wanneer een interface wordt aangevraagd, wordt het aantal verwijzingen verhoogd. Een toepassing geeft een interface vrij wanneer deze interface niet meer nodig is, waardoor de referentietelling wordt verlaagd. Zolang het aantal verwijzingen groter is dan nul, blijft het object in het geheugen. Wanneer het aantal verwijzingen nul bereikt, vernietigt het object zichzelf. U hoeft niets te weten over het aantal verwijzingen van een object. Zolang u de interfaces van een object op de juiste manier verkrijgt en loslaat, heeft het object de juiste levensduur.

Het correct verwerken van referentietellingen is een cruciaal onderdeel van COM-programmering. Als u dit niet doet, kunt u eenvoudig een geheugenlek of een crash maken. Een van de meest voorkomende fouten die COM-programmeurs maken, is het niet vrijgeven van een interface. Wanneer dit gebeurt, bereikt het aantal verwijzingen nooit nul en blijft het object voor onbepaalde tijd in het geheugen.

Notitie

Direct3D 10 of hoger heeft enigszins gewijzigde levensduurregels voor objecten. Met name overleven objecten die zijn afgeleid van ID3DxxDeviceChild hun ouderapparaat niet, dat wil zeggen dat als de eigenaar, ID3DxxDevice, een 0 refcount bereikt, alle ondergeschikte objecten ook onmiddellijk ongeldig zijn. Ook, wanneer u de methoden Stel gebruikt om objecten aan de renderpijplijn te binden, verhogen deze verwijzingen niet de referentietelling, dat wil zeggen, het zijn zwakke verwijzingen. In de praktijk wordt dit het beste afgehandeld door ervoor te zorgen dat u alle onderliggende objecten van het apparaat volledig loslaat voordat u het apparaat loslaat.

Het aantal verwijzingen verhogen en verlagen

Wanneer u een nieuwe interfaceaanwijzer krijgt, moet het aantal verwijzingen worden verhoogd door een aanroep naar IUnknown::AddRef. Uw toepassing hoeft deze methode echter meestal niet aan te roepen. Als u een interfaceaanwijzer verkrijgt door een methode voor het maken van objecten aan te roepen of door IUnknown::QueryInterfaceaan te roepen, wordt het verwijzingsaantal automatisch verhoogd. Als u echter een interfaceaanwijzer op een andere manier maakt, zoals het kopiëren van een bestaande aanwijzer, moet u expliciet IUnknown::AddRef-aanroepen. Als u de oorspronkelijke interfacepointer loslaat, kan het object worden vernietigd, ook al moet u mogelijk nog steeds de kopie van de aanwijzer gebruiken.

U moet alle interfacepointers vrijgeven, ongeacht of u of het object het referentieaantal heeft verhoogd. Wanneer u geen interfaceaanwijzer meer nodig hebt, roept u IUnknown::Release aan om het aantal verwijzingen te verlagen. Een veelvoorkomende procedure is om alle interface-aanwijzers te initialiseren naar nullptren deze vervolgens weer in te stellen op nullptr wanneer ze worden vrijgegeven. Met deze conventie kunt u alle interfacepointers in uw opschooncode testen. Degenen die niet nullptr zijn nog steeds actief en u moet ze vrijgeven voordat u de toepassing beëindigt.

Het volgende codefragment breidt het eerder weergegeven voorbeeld uit om te laten zien hoe je referentietelling kunt verwerken.

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;
}

COM Smart Pointers

De code heeft tot nu toe expliciet Release en AddRef aangeroepen om de referentieaantallen te onderhouden met behulp van IUnknown methoden. Dit patroon vereist dat de programmeur ijverig is bij het onthouden van de telling in alle mogelijke codepaden. Dit kan leiden tot gecompliceerde foutafhandeling en als C++ uitzonderingsafhandeling is ingeschakeld, kan het bijzonder moeilijk zijn om te implementeren. Een betere oplossing met C++ is om gebruik te maken van een slimme aanwijzer.

  • winrt::com_ptr is een slimme aanwijzer die wordt geleverd door de C++/WinRT-taalprojecties. Dit is de aanbevolen COM-slimme aanwijzer voor UWP-apps. Voor C++/WinRT is C++17 vereist.

  • Microsoft::WRL::ComPtr is een slimme aanwijzer die wordt geleverd door de WRL-sjabloonbibliotheek (Windows Runtime C++). Deze bibliotheek is 'puur' C++ zodat deze kan worden gebruikt voor Windows Runtime-toepassingen (via C++/CX of C++/WinRT) en Win32-bureaubladtoepassingen. Deze slimme aanwijzer werkt ook in oudere versies van Windows die geen ondersteuning bieden voor de Windows Runtime-API's. Voor Win32-bureaubladtoepassingen kunt u #include <wrl/client.h> gebruiken om deze klasse alleen op te nemen en eventueel ook het preprocessorsymbool __WRL_CLASSIC_COM_STRICT__ te definiëren. Zie COM smart pointers opnieuw bekekenvoor meer informatie.

  • CComPtr- is een slimme aanwijzer die wordt geleverd door de ATL-(Active Template Library). De Microsoft::WRL::ComPtr is een nieuwere versie van deze implementatie die een aantal subtiele gebruiksproblemen aanpakt, dus het gebruik van deze slimme aanwijzer wordt niet aanbevolen voor nieuwe projecten. Zie CComPtr- en CComQIPtr-maken en gebruiken voor meer informatie.

ATL gebruiken met DirectX 9

Als u de Active Template Library (ATL) met DirectX 9 wilt gebruiken, moet u de interfaces voor ATL-compatibiliteit opnieuw definiëren. Hierdoor kunt u de CComQIPtr--klasse correct gebruiken om een aanwijzer naar een interface te verkrijgen.

U weet of u de interfaces voor ATL niet opnieuw definieert, omdat u het volgende foutbericht ziet.

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

In het volgende codevoorbeeld ziet u hoe u de interface IDirectXFileData definieert.

// 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);

Nadat u de interface opnieuw hebt gedefinieerd, moet u de methode Koppelen gebruiken om de interface te koppelen aan de interfaceaanwijzer die wordt geretourneerd door ::D irect3DCreate9. Als u dit niet doet, wordt de IDirect3D9-interface niet correct vrijgegeven door de smart pointer-klasse.

De CComPtr klasse roept intern IUnknown::AddRef- aan op de interfaceaanwijzer wanneer het object wordt gemaakt en wanneer er een interface wordt toegewezen aan de CComPtr--klasse. Om te voorkomen dat de interfaceaanwijzer wordt gelekt, roep **IUnknown::AddRef niet aan op de interface die wordt geretourneerd door ::Direct3DCreate9-.

Met de volgende code wordt de interface correct vrijgegeven zonder IUnknown::AddRefaan te roepen.

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

Gebruik de vorige code. Gebruik niet de volgende code, die IUnknown::AddRef gevolgd door IUnknown::Releaseaanroept en de verwijzing die is toegevoegd door ::Direct3DCreate9niet vrijgeeft.

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

Houd er rekening mee dat dit de enige plaats is in Direct3D 9 waar u de methode Attach op deze manier moet gebruiken.

Zie de definities in het Atlbase.h headerbestand voor meer informatie over de CComPTR- en CComQIPtr klassen.