Delen via


COM-coderingsprocedures

In dit onderwerp worden manieren beschreven om uw COM-code effectiever en robuuster te maken.

De operator __uuidof

Wanneer u uw programma bouwt, kunnen er koppelingsfouten optreden die vergelijkbaar zijn met de volgende:

unresolved external symbol "struct _GUID const IID_IDrawable"

Deze fout betekent dat een GUID-constante is gedeclareerd met externe koppeling (extern) en dat de linker de definitie van de constante niet kan vinden. De waarde van een GUID-constante wordt meestal geëxporteerd uit een statisch bibliotheekbestand. Als u Microsoft Visual C++ gebruikt, kunt u voorkomen dat u een statische bibliotheek hoeft te koppelen met behulp van de operator __uuidof. Deze operator is een Microsoft-taalextensie. Het retourneert een GUID-waarde uit een expressie. De expressie kan een interfacetypenaam, een klassenaam of een interfaceaanwijzer zijn. Met __uuidofkunt u het dialoogvenster Algemene items als volgt maken:

IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL, 
    __uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));

De compiler extraheert de GUID-waarde uit de header, dus er is geen bibliotheekexport nodig.

Notitie

De GUID-waarde is gekoppeld aan de typenaam door __declspec(uuid( ... )) in de header te declareren. Zie de documentatie voor __declspec in de Visual C++-documentatie voor meer informatie.

 

De macro IID_PPV_ARGS

We hebben gezien dat zowel CoCreateInstance als QueryInterface de laatste parameter moeten worden verplicht tot een void** type. Hierdoor ontstaat de kans dat een type niet overeenkomt. Bekijk het volgende codefragment:

// Wrong!

IFileOpenDialog *pFileOpen;

hr = CoCreateInstance(
    __uuidof(FileOpenDialog), 
    NULL, 
    CLSCTX_ALL, 
    __uuidof(IFileDialogCustomize),       // The IID does not match the pointer type!
    reinterpret_cast<void**>(&pFileOpen)  // Coerce to void**.
    );

Deze code vraagt om de IFileDialogCustomize interface, maar geeft een IFileOpenDialog aanwijzer door. De reinterpret_cast-expressie omzeilt het C++-typesysteem, zodat de compiler deze fout niet onderschept. Als het object de aangevraagde interface niet implementeert, mislukt de aanroep in het beste geval. In het ergste geval slaagt de functie en hebt u een niet-overeenkomende aanwijzer. Met andere woorden, het type aanwijzer komt niet overeen met de werkelijke vtable in het geheugen. Zoals u zich kunt voorstellen, kan er op dat moment niets goeds gebeuren.

Notitie

Een vtable (virtuele methodetabel) is een tabel met functiepointers. De vtable is hoe COM een methode-aanroep koppelt aan de implementatie tijdens runtime. Vtables zijn niet toevallig hoe de meeste C++-compilers virtuele methoden implementeren.

 

De IID_PPV_ARGS macro helpt deze foutklasse te voorkomen. Als u deze macro wilt gebruiken, vervangt u de volgende code:

__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)

hiermee:

IID_PPV_ARGS(&pFileOpen)

De macro voegt automatisch __uuidof(IFileOpenDialog) in voor de interface-id, zodat deze gegarandeerd overeenkomt met het type aanwijzer. Dit is de gewijzigde (en juiste) code:

// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL, 
    IID_PPV_ARGS(&pFileOpen));

U kunt dezelfde macro gebruiken met QueryInterface:

IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));

Het SafeRelease-patroon

Het tellen van verwijzingen is een van deze dingen in programmeren die in feite eenvoudig is, maar ook tijdrovend is, waardoor het gemakkelijk is om fout te gaan. Veelvoorkomende fouten zijn:

  • Er kan geen interfaceaanwijzer worden vrijgegeven wanneer u klaar bent met het gebruik ervan. Deze foutklasse zorgt ervoor dat uw programma geheugen en andere resources lekt, omdat objecten niet worden vernietigd.
  • Het aanroepen van release- met een ongeldige aanwijzer. Deze fout kan bijvoorbeeld optreden als het object nooit is gemaakt. Deze foutcategorie zorgt er waarschijnlijk voor dat uw programma vastloopt.
  • Deductie van een interfaceaanwijzer nadat Release wordt aangeroepen. Deze fout kan ertoe leiden dat uw programma vastloopt. Erger nog, het kan ertoe leiden dat uw programma op een willekeurige tijd vastloopt, waardoor het moeilijk is om de oorspronkelijke fout op te sporen.

Een manier om deze bugs te voorkomen, is door Release- aan te roepen via een functie waarmee de aanwijzer veilig wordt vrijgegeven. De volgende code toont een functie die dit doet:

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

Deze functie gebruikt een COM-interfacepointer als parameter en doet het volgende:

  1. Controleert of de aanwijzer NULL-is.
  2. Roept release- aan als de aanwijzer niet NULL-is.
  3. Hiermee stelt u de aanwijzer in op NULL-.

Hier volgt een voorbeeld van het gebruik van SafeRelease:

void UseSafeRelease()
{
    IFileOpenDialog *pFileOpen = NULL;

    HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, 
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
    if (SUCCEEDED(hr))
    {
        // Use the object.
    }
    SafeRelease(&pFileOpen);
}

Als CoCreateInstance slaagt, wordt de aanwijzer voor SafeRelease vrijgegeven. Als CoCreateInstance mislukt, blijft pFileOpenNULL-. De functie SafeRelease controleert dit en slaat de aanroep naar Release-over.

Het is ook veilig om SafeRelease meer dan één keer op dezelfde aanwijzer aan te roepen, zoals hier wordt weergegeven:

// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);

COM Smart Pointers

De functie SafeRelease is nuttig, maar hiervoor moet u twee dingen onthouden:

  • Initialiseer elke interfacepointer naar NULL-.
  • Roep SafeRelease aan voordat elke aanwijzer buiten het bereik valt.

Als C++-programmeur denkt u waarschijnlijk dat u geen van deze dingen hoeft te onthouden. Daarom heeft C++ constructors en destructors. Het zou leuk zijn om een klasse te hebben waarmee de onderliggende interfacepointer wordt verpakt en de aanwijzer automatisch wordt geïnitialiseerd en vrijgegeven. Met andere woorden, we willen zoiets als volgt:

// Warning: This example is not complete.

template <class T>
class SmartPointer
{
    T* ptr;

 public:
    SmartPointer(T *p) : ptr(p) { }
    ~SmartPointer()
    {
        if (ptr) { ptr->Release(); }
    }
};

De hier weergegeven klassedefinitie is onvolledig en kan niet worden gebruikt zoals wordt weergegeven. U moet minimaal een kopieerconstructor, een toewijzingsoperator en een manier definiëren om toegang te krijgen tot de onderliggende COM-aanwijzer. Gelukkig hoeft u dit werk niet uit te voeren, omdat Microsoft Visual Studio al een slimme aanwijzerklasse biedt als onderdeel van de Active Template Library (ATL).

De ATL smart pointer-klasse heeft de naam CComPtr-. (Er is ook een CComQIPtr klasse, die hier niet wordt besproken.) Hier ziet u het dialoogvenster openen voorbeeld dat is herschreven om CComPtr-te gebruiken.

#include <windows.h>
#include <shobjidl.h> 
#include <atlbase.h> // Contains the declaration of CComPtr.

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | 
        COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        CComPtr<IFileOpenDialog> pFileOpen;

        // Create the FileOpenDialog object.
        hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
        if (SUCCEEDED(hr))
        {
            // Show the Open dialog box.
            hr = pFileOpen->Show(NULL);

            // Get the file name from the dialog box.
            if (SUCCEEDED(hr))
            {
                CComPtr<IShellItem> pItem;
                hr = pFileOpen->GetResult(&pItem);
                if (SUCCEEDED(hr))
                {
                    PWSTR pszFilePath;
                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);

                    // Display the file name to the user.
                    if (SUCCEEDED(hr))
                    {
                        MessageBox(NULL, pszFilePath, L"File Path", MB_OK);
                        CoTaskMemFree(pszFilePath);
                    }
                }

                // pItem goes out of scope.
            }

            // pFileOpen goes out of scope.
        }
        CoUninitialize();
    }
    return 0;
}

Het belangrijkste verschil tussen deze code en het oorspronkelijke voorbeeld is dat deze versie niet expliciet Release-aanroept. Wanneer het CComPtr exemplaar buiten het bereik valt, roept de destructor Release- aan op de onderliggende aanwijzer.

CComPtr- is een klassesjabloon. Het argument sjabloon is het COM-interfacetype. Intern bevat CComPtr- een aanwijzer van dat type. CComPtr overschrijft operator->() en operator&() zodat de klasse fungeert als de onderliggende aanwijzer. De volgende code is bijvoorbeeld gelijk aan het aanroepen van de methode IFileOpenDialog::Toon methode rechtstreeks:

hr = pFileOpen->Show(NULL);

CComPtr- definieert ook een CComPtr::CoCreateInstance methode, die de com-CoCreateInstance-functie aanroept met een aantal standaardparameterwaarden. De enige vereiste parameter is de klasse-id, zoals in het volgende voorbeeld wordt weergegeven:

hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));

De CComPtr::CoCreateInstance methode wordt uitsluitend als gemak geboden; u kunt de functie COM CoCreateInstance nog steeds aanroepen, indien gewenst.

Volgend

foutafhandeling in COM-