COM-Codierungsmethoden

In diesem Thema werden Möglichkeiten beschrieben, ihren COM-Code effektiver und robuster zu gestalten.

Der __uuidof-Operator

Wenn Sie Ihr Programm erstellen, erhalten Sie möglicherweise Linkerfehler wie die folgenden:

unresolved external symbol "struct _GUID const IID_IDrawable"

Dieser Fehler bedeutet, dass eine GUID-Konstante mit externer Verknüpfung (extern) deklariert wurde und der Linker die Definition der Konstante nicht finden konnte. Der Wert einer GUID-Konstante wird in der Regel aus einer statischen Bibliotheksdatei exportiert. Wenn Sie Microsoft Visual C++ verwenden, können Sie das Verknüpfen einer statischen Bibliothek vermeiden, indem Sie den operator __uuidof verwenden. Dieser Operator ist eine Microsoft-Spracherweiterung. Es gibt einen GUID-Wert aus einem Ausdruck zurück. Der Ausdruck kann ein Schnittstellentypname, ein Klassenname oder ein Schnittstellenzeiger sein. Mit __uuidof können Sie das Common Item Dialog-Objekt wie folgt erstellen:

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

Der Compiler extrahiert den GUID-Wert aus dem Header, sodass kein Bibliotheksexport erforderlich ist.

Hinweis

Der GUID-Wert wird dem Typnamen zugeordnet, indem im Header deklariert __declspec(uuid( ... )) wird. Weitere Informationen finden Sie in der Dokumentation zu __declspec in der Visual C++-Dokumentation.

 

Das IID_PPV_ARGS-Makro

Wir haben gesehen, dass sowohl CoCreateInstance als auch QueryInterface die Koercierung des endgültigen Parameters in einen void** -Typ erfordern. Dadurch entsteht das Potenzial für einen Typkonflikt. Betrachten Sie das folgende 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**.
    );

Dieser Code fragt nach der IFileDialogCustomize-Schnittstelle , übergibt jedoch einen IFileOpenDialog-Zeiger . Der reinterpret_cast Ausdruck umgeht das C++-Typsystem, sodass der Compiler diesen Fehler nicht abfangen kann. Wenn das Objekt im besten Fall die angeforderte Schnittstelle nicht implementiert, schlägt der Aufruf einfach fehl. Im schlimmsten Fall ist die Funktion erfolgreich, und Sie haben einen nicht übereinstimmenden Zeiger. Anders ausgedrückt: Der Zeigertyp stimmt nicht mit der tatsächlichen VTable im Arbeitsspeicher überein. Wie Sie sich vorstellen können, kann an diesem Punkt nichts Gutes passieren.

Hinweis

Eine vtable (Virtuelle Methodentabelle) ist eine Tabelle mit Funktionszeigern. Die vtable ist, wie COM einen Methodenaufruf zur Laufzeit an seine Implementierung bindet. Nicht zufällig sind vtables, wie die meisten C++-Compiler virtuelle Methoden implementieren.

 

Das IID_PPV_ARGS-Makro hilft, diese Fehlerklasse zu vermeiden. Ersetzen Sie den folgenden Code, um dieses Makro zu verwenden:

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

durch diesen:

IID_PPV_ARGS(&pFileOpen)

Das Makro fügt __uuidof(IFileOpenDialog) automatisch für den Schnittstellenbezeichner ein, sodass es garantiert mit dem Zeigertyp übereinstimmt. Hier ist der geänderte (und richtige) Code:

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

Sie können dasselbe Makro mit QueryInterface verwenden:

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

Das SafeRelease-Muster

Die Referenzzählung ist eine dieser Dinge in der Programmierung, die im Grunde einfach, aber auch mühsam ist, was es leicht macht, falsch zu sein. Typische Fehler sind:

  • Fehler beim Freigeben eines Schnittstellenzeigers, wenn Sie mit der Verwendung fertig sind. Diese Fehlerklasse führt dazu, dass Ihr Programm Arbeitsspeicher und andere Ressourcen verloren geht, da Objekte nicht zerstört werden.
  • Aufrufen von Release mit einem ungültigen Zeiger. Dieser Fehler kann beispielsweise auftreten, wenn das Objekt nie erstellt wurde. Diese Fehlerkategorie führt wahrscheinlich dazu, dass Ihr Programm abstürzt.
  • Dereferenzieren eines Schnittstellenzeigers nach dem Aufrufen von Release . Dieser Fehler kann dazu führen, dass Ihr Programm abstürzt. Schlimmer noch: Es kann dazu führen, dass Ihr Programm zu einem späteren Zeitpunkt zufällig abstürzt, sodass es schwierig ist, den ursprünglichen Fehler aufzuspüren.

Eine Möglichkeit, diese Fehler zu vermeiden, besteht darin , Release über eine Funktion aufzurufen, die den Zeiger sicher freigibt. Der folgende Code zeigt eine Funktion, die dies tut:

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

Diese Funktion verwendet einen COM-Schnittstellenzeiger als Parameter und führt Folgendes aus:

  1. Überprüft, ob der Zeiger NULL ist.
  2. Ruft Release auf, wenn der Zeiger nicht NULL ist.
  3. Legt den Zeiger auf NULL fest.

Hier ist ein Beispiel für die Verwendung SafeReleasevon :

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

Wenn CoCreateInstance erfolgreich ist, gibt der Aufruf von den SafeRelease Zeiger frei. Wenn CoCreateInstance fehlschlägt , bleibt pFileOpenNULL. Die SafeRelease Funktion überprüft dies und überspringt den Aufruf von Release.

Es ist auch sicher, mehr als einmal mit demselben Zeiger aufzurufen SafeRelease , wie hier gezeigt:

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

INTELLIGENTE COM-Zeiger

Die SafeRelease Funktion ist nützlich, aber Sie müssen sich zwei Dinge merken:

  • Initialisieren Sie jeden Schnittstellenzeiger auf NULL.
  • Rufen Sie auf SafeRelease , bevor jeder Zeiger den Gültigkeitsbereich überschreitet.

Als C++-Programmierer denken Sie wahrscheinlich, dass Sie sich keines dieser Dinge merken sollten. Schließlich verfügt C++ aus diesem Grund über Konstruktoren und Destruktoren. Es wäre schön, eine Klasse zu haben, die den zugrunde liegenden Schnittstellenzeiger umschließt und den Zeiger automatisch initialisiert und freigibt. Mit anderen Worten, wir möchten etwas wie folgt:

// Warning: This example is not complete.

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

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

Die hier gezeigte Klassendefinition ist unvollständig und kann nicht wie dargestellt verwendet werden. Sie müssen mindestens einen Kopierkonstruktor, einen Zuweisungsoperator und eine Möglichkeit für den Zugriff auf den zugrunde liegenden COM-Zeiger definieren. Glücklicherweise müssen Sie diese Arbeit nicht ausführen, da Microsoft Visual Studio bereits eine intelligente Zeigerklasse als Teil der Aktiven Vorlagenbibliothek (Active Template Library, ATL) bereitstellt.

Die INTELLIGENTE ZEIGER-Klasse von ATL heißt CComPtr. (Es gibt auch eine CComQIPtr-Klasse , die hier nicht erläutert wird.) Hier sehen Sie das Open Dialog Box-Beispiel , das für die Verwendung von CComPtr umgeschrieben wurde.

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

Der Standard Unterschied zwischen diesem Code und dem ursprünglichen Beispiel besteht darin, dass diese Version Release nicht explizit aufruft. Wenn der CComPtr-instance den Gültigkeitsbereich überschreitet, ruft der Destruktor Release für den zugrunde liegenden Zeiger auf.

CComPtr ist eine Klassenvorlage. Das Vorlagenargument ist der COM-Schnittstellentyp. Intern enthält CComPtr einen Zeiger dieses Typs. CComPtr überschreibt operator->() und operator&(), sodass die Klasse wie der zugrunde liegende Zeiger fungiert. Der folgende Code entspricht beispielsweise dem direkten Aufrufen der IFileOpenDialog::Show-Methode :

hr = pFileOpen->Show(NULL);

CComPtr definiert auch eine CComPtr::CoCreateInstance-Methode , die die COM CoCreateInstance-Funktion mit einigen Standardwerten aufruft. Der einzige erforderliche Parameter ist der Klassenbezeichner, wie das nächste Beispiel zeigt:

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

Die CComPtr::CoCreateInstance-Methode wird nur aus Gründen der Einfachheit bereitgestellt. Sie können die COM CoCreateInstance-Funktion weiterhin aufrufen, wenn Sie dies bevorzugen.

Nächste

Fehlerbehandlung in COM