QueryInterface: Navigeren in een object

Tip

Moderne QueryInterface-patronen: gebruik IID_PPV_ARGS en slimme aanwijzers. Onbewerkte QueryInterface aanroepen zijn foutgevoelig (niet-overeenkomende IID en aanwijzertype). Moderne C++-code moet typeveilige helpers gebruiken:

#include <wrl/client.h>  // Microsoft::WRL::ComPtr

Microsoft::WRL::ComPtr<IUnknown> unknown = /* ... */;
Microsoft::WRL::ComPtr<IPersistFile> persistFile;

// ✅ Best — ComPtr::As() handles QI + type safety + Release automatically
HRESULT hr = unknown.As(&persistFile);

// ✅ Good — IID_PPV_ARGS macro ensures IID matches the pointer type
hr = unknown->QueryInterface(IID_PPV_ARGS(&persistFile));

// ❌ Dangerous — IID and pointer type can mismatch silently
hr = unknown->QueryInterface(IID_IPersistFile, (void**)&persistFile);

C++/WinRT-equivalent:

#include <winrt/base.h>

winrt::com_ptr<IUnknown> unknown = /* ... */;
auto persistFile = unknown.as<IPersistFile>();      // throws on failure
auto maybePF = unknown.try_as<IPersistFile>();      // returns nullptr on failure

Belangrijke regels: Nooit interfacepointers casten zonder QueryInterface — COM-identiteitsregels vereisen dat elke interfacepointer wordt verkregen via QI of CoCreateInstance. Directe casts (static_cast, reinterpret_cast) produceren niet-gedefinieerd gedrag.

Nadat u een initiële aanwijzer op een interface op een object hebt, heeft COM een zeer eenvoudig mechanisme om erachter te komen of het object een andere specifieke interface ondersteunt en, als dat het zo is, een aanwijzer naar het object krijgt. (Zie Een aanwijzer naar een object ophalen voor informatie over het verkrijgen van een initiële aanwijzer naar een interface op een object.) Dit mechanisme is de methode QueryInterface van de interface IUnknown. Als het object de aangevraagde interface ondersteunt, moet de methode een aanwijzer naar die interface retourneren. Hierdoor kan een object vrij navigeren door de interfaces die een object ondersteunt. QueryInterface scheidt de aanvraag 'Ondersteunt u een bepaald contract?' van het krachtige gebruik van dat contract zodra de onderhandelingen zijn geslaagd.

Wanneer een client in eerste instantie toegang krijgt tot een object, ontvangt die client minimaal een IUnknown-interfaceaanwijzer (de meest fundamentele interface) waarmee het de levensduur van het object kan beheren, door het object te vertellen wanneer het wordt gedaan met behulp van het object en QueryInterface aan te roepen. De client is geprogrammeerd om elk object te vragen dat het beheert om bepaalde bewerkingen uit te voeren, maar de IUnknown-interface heeft geen functies voor deze bewerkingen. In plaats daarvan worden deze bewerkingen uitgedrukt via andere interfaces. De klant is dus geprogrammeerd om met objecten voor deze interfaces te onderhandelen. De client roept QueryInterface aan om een object te vragen voor een interface waarmee de client de gewenste bewerkingen kan aanroepen.

Omdat het object QueryInterface implementeert, heeft het de mogelijkheid om de aanvraag te accepteren of te weigeren. Als het object de aanvraag van de client accepteert, retourneert QueryInterface een nieuwe aanwijzer naar de aangevraagde interface naar de client. Via die interfacepointer heeft de client toegang tot de methoden van die interface. Als het object daarentegen de aanvraag van de client weigert, retourneert QueryInterface een null-aanwijzer( een fout) en heeft de client geen aanwijzer waarmee de gewenste functies kunnen worden aangeroepen. In dit geval moet de client daar gracieus mee omgaan. Stel dat een client een aanwijzer heeft naar interface A op een object en vraagt om interfaces B en C. Stel dat het object interface B ondersteunt, maar geen interface C ondersteunt. Het resultaat is dat het object een aanwijzer retourneert naar B en rapporteert dat C niet wordt ondersteund.

Een belangrijk punt is dat wanneer een object een aanroep naar QueryInterface weigert, het onmogelijk is voor de client om het object te vragen de bewerkingen uit te voeren die zijn uitgedrukt via de aangevraagde interface. Een client moet een interfaceaanwijzer hebben om methoden in die interface aan te roepen. Als het object weigert de aangevraagde aanwijzer te verstrekken, moet de client bereid zijn om dit te doen zonder, hetzij door niet te doen wat het had bedoeld om met dat object te doen of door terug te vallen op een andere, misschien minder krachtige interface. Deze functie van COM-functionaliteit werkt goed in vergelijking met andere objectgeoriënteerde systemen waarin u niet kunt weten of een functie werkt totdat u die functie aanroept, en zelfs dan is het afhandelen van fouten onzeker. QueryInterface biedt een betrouwbare en consistente manier om te weten of een object een interface ondersteunt voordat de methoden worden aangeroepen.

De QueryInterface-methode biedt ook een robuuste en betrouwbare manier voor een object om aan te geven dat het geen ondersteuning biedt voor een bepaald contract. Dat wil zeggen, als in een aanroep van QueryInterface een 'oud' object vraagt of het een 'nieuwe' interface ondersteunt (bijvoorbeeld die is uitgevonden nadat het oude object is verzonden), zal het oude object betrouwbaar zijn, zonder een crash te veroorzaken, antwoord 'nee'. De technologie die dit ondersteunt, is het algoritme waarmee IID's worden toegewezen. Hoewel dit misschien een klein punt lijkt, is het uiterst belangrijk voor de algehele architectuur van het systeem en de mogelijkheid om verouderde elementen over nieuwe functionaliteit te vragen, is verrassend een functie die niet aanwezig is in de meeste andere objectarchitecturen.

IUnknown gebruiken en implementeren