COM-kodningsmetoder

I det här avsnittet beskrivs olika sätt att göra DIN COM-kod mer effektiv och robust.

Operatorn __uuidof

När du skapar programmet kan du få länkfel som liknar följande:

unresolved external symbol "struct _GUID const IID_IDrawable"

Det här felet innebär att en GUID-konstant deklarerades med extern länkning (extern), och att länkaren inte kunde hitta definitionen av konstanten. Värdet för en GUID-konstant exporteras vanligtvis från en statisk biblioteksfil. Om du använder Microsoft Visual C++kan du undvika behovet av att länka ett statiskt bibliotek med hjälp av operatorn __uuidof. Den här operatorn är ett Microsoft-språktillägg. Det returnerar ett GUID-värde från ett uttryck. Uttrycket kan vara ett gränssnittstypnamn, ett klassnamn eller en gränssnittspekare. Med hjälp av __uuidofkan du skapa dialogobjektet Common Item enligt följande:

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

Kompilatorn extraherar GUID-värdet från rubriken, så det krävs ingen biblioteksexport.

Not

GUID-värdet associeras med typnamnet genom att deklarera __declspec(uuid( ... )) i rubriken. Mer information finns i dokumentationen för __declspec i Visual C++-dokumentationen.

 

Makrot IID_PPV_ARGS

Vi såg att både CoCreateInstance och QueryInterface kräva att den sista parametern tvingas till en void** typ. Detta skapar potentialen för en typmatchningsfel. Överväg följande kodfragment:

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

Den här koden frågar efter gränssnittet IFileDialogCustomize, men skickar en IFileOpenDialog pekare. reinterpret_cast-uttrycket kringgår C++-typsystemet, så kompilatorn kommer inte att fånga upp det här felet. Om objektet i bästa fall inte implementerar det begärda gränssnittet misslyckas anropet helt enkelt. I värsta fall lyckas funktionen och du har en felmatchad pekare. Med andra ord matchar pekartypen inte den faktiska vtable-tabellen i minnet. Som ni kan föreställa er kan inget bra hända vid den tidpunkten.

Not

En vtable- (virtuell metodtabell) är en tabell med funktionspekare. Den virtuella tabellen är hur COM binder ett metodanrop till implementeringen vid körning. Inte av en tillfällighet är virtuella datorer hur de flesta C++-kompilatorer implementerar virtuella metoder.

 

Det IID_PPV_ARGS makrot hjälper till att undvika den här felklassen. Om du vill använda det här makrot ersätter du följande kod:

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

med detta:

IID_PPV_ARGS(&pFileOpen)

Makrot infogar automatiskt __uuidof(IFileOpenDialog) för gränssnittsidentifieraren, så det är garanterat att matcha pekartypen. Här är den ändrade (och korrekta) koden:

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

Du kan använda samma makro med QueryInterface:

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

SafeRelease-mönstret

Referensräkning är en av de saker i programmering som i princip är enkel, men också omständlig, vilket gör det enkelt att få fel. Vanliga fel är:

  • Det går inte att släppa en gränssnittspekare när du är klar med att använda den. Den här felklassen gör att programmet läcker minne och andra resurser eftersom objekt inte förstörs.
  • Anropar Release med en ogiltig pekare. Det här felet kan till exempel inträffa om objektet aldrig skapades. Den här kategorin av bugg kommer förmodligen att orsaka att programmet kraschar.
  • Dereferencing en gränssnittspekare efter Release anropas. Den här buggen kan orsaka att programmet kraschar. Värre är att det kan orsaka att programmet kraschar slumpmässigt senare, vilket gör det svårt att spåra det ursprungliga felet.

Ett sätt att undvika dessa buggar är att anropa Release via en funktion som släpper pekaren på ett säkert sätt. Följande kod visar en funktion som gör detta:

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

Den här funktionen tar en COM-gränssnittspekare som en parameter och gör följande:

  1. Kontrollerar om pekaren är NULL-.
  2. Anropar Release om pekaren inte är NULL-.
  3. Anger pekaren till NULL-.

Här är ett exempel på hur du använder 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);
}

Om CoCreateInstance- lyckas släpper anropet till SafeRelease pekaren. Om CoCreateInstance misslyckas förblir pFileOpenNULL-. Funktionen SafeRelease söker efter detta och hoppar över anropet till Release.

Det är också säkert att anropa SafeRelease mer än en gång på samma pekare, som du ser här:

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

SMARTA COM-pekare

Funktionen SafeRelease är användbar, men du måste komma ihåg två saker:

  • Initiera varje gränssnittspekare för att NULL-.
  • Anropa SafeRelease innan varje pekare hamnar utanför omfånget.

Som C++-programmerare tänker du förmodligen att du inte ska behöva komma ihåg någon av dessa saker. Det är trots allt därför C++ har konstruktorer och destruktörer. Det skulle vara trevligt att ha en klass som omsluter den underliggande gränssnittspekaren och automatiskt initierar och släpper pekaren. Med andra ord vill vi ha något som liknar detta:

// Warning: This example is not complete.

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

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

Klassdefinitionen som visas här är ofullständig och kan inte användas som det visas. Du måste minst definiera en kopieringskonstruktor, en tilldelningsoperator och ett sätt att komma åt den underliggande COM-pekaren. Som tur är behöver du inte utföra något av det här arbetet eftersom Microsoft Visual Studio redan tillhandahåller en smart pekarklass som en del av ATL (Active Template Library).

KLASSEN FÖR SMARTA ATL-pekare heter CComPtr. (Det finns också en CComQIPtr-klass, som inte beskrivs här.) Här är öppna dialogrutan exempel omskriven för att använda CComPtr.

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

Den största skillnaden mellan den här koden och det ursprungliga exemplet är att den här versionen inte uttryckligen anropar Release. När den CComPtr--instansen hamnar utanför omfånget anropar destructor Release på den underliggande pekaren.

CComPtr är en klassmall. Mallargumentet är COM-gränssnittstypen. Internt innehåller CComPtr en pekare av den typen. CComPtr åsidosätteroperator>() och operatorn&() så att klassen fungerar som den underliggande pekaren. Följande kod motsvarar till exempel anropet IFileOpenDialog::Visa-metoden direkt:

hr = pFileOpen->Show(NULL);

CComPtr definierar även en CComPtr::CoCreateInstance-metod, som anropar funktionen COM CoCreateInstance med några standardparametervärden. Den enda obligatoriska parametern är klassidentifieraren, som nästa exempel visar:

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

Metoden CComPtr::CoCreateInstance tillhandahålls enbart som en bekvämlighet. Du kan fortfarande anropa funktionen COM CoCreateInstance om du vill.

Nästa

felhantering i COM-