Freigeben über


COM-Codierungsmethoden

In diesem Thema wird beschrieben, wie Sie Ihren COM-Code effektiver und robuster gestalten können.

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 deklariert wurde (extern), und der Linker konnte die Definition der Konstante nicht finden. Der Wert einer GUID-Konstante wird in der Regel aus einer statischen Bibliotheksdatei exportiert. Wenn Sie Microsoft Visual C++ verwenden, können Sie die Verknüpfung einer statischen Bibliothek vermeiden, indem Sie den __uuidof-Operator verwenden. Dieser Operator ist eine Microsoft-Spracherweiterung. Er gibt einen GUID-Wert aus einem Ausdruck zurück. Der Ausdruck kann ein Schnittstellentypname, ein Klassenname oder ein Schnittstellenzeiger sein. Mit __uuidofkö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.

Anmerkung

Der GUID-Wert ist dem Typnamen zugeordnet, indem __declspec(uuid( ... )) im Header deklariert 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- erfordern, dass der letzte Parameter in einen void**- Typ umsetzt. 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 fordert die IFileDialogCustomize Schnittstelle an, übergibt jedoch einen IFileOpenDialog Zeiger. Der reinterpret_cast Ausdruck umgangen das C++-Typsystem, sodass der Compiler diesen Fehler nicht abfangen wird. Wenn das Objekt die angeforderte Schnittstelle nicht implementiert, schlägt der Aufruf im besten Fall fehl. Im schlimmsten Fall ist die Funktion erfolgreich, und Sie haben einen nicht übereinstimmenden Zeiger. Mit anderen Worten, 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.

Anmerkung

Eine vtable (virtuelle Methodentabelle) ist eine Tabelle mit Funktionszeigern. Die vtable stellt fest, wie COM einen Methodenaufruf zur Laufzeit an seine Implementierung bindet. VTables sind nicht zufällig, 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)

hiermit:

IID_PPV_ARGS(&pFileOpen)

Das Makro fügt automatisch __uuidof(IFileOpenDialog) für den Schnittstellenbezeichner ein, sodass es garantiert dem Zeigertyp entspricht. 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 Verweiszählung ist eines der Dinge bei der Programmierung, die im Grunde einfach ist, aber auch mühsam ist, was es leicht macht, falsch zu werden. Typische Fehler sind:

  • Fehler beim Freigeben eines Schnittstellenzeigers, wenn Sie damit fertig sind. Diese Fehlerklasse führt dazu, dass Ihr Programm Arbeitsspeicher und andere Ressourcen verloren geht, da Objekte nicht zerstört werden.
  • Aufrufen Release- mit einem ungültigen Zeiger. Dieser Fehler kann beispielsweise auftreten, wenn das Objekt nie erstellt wurde. Diese Kategorie des Fehlers führt wahrscheinlich dazu, dass Ihr Programm abstürzt.
  • Das Ableiten eines Schnittstellenzeigers nach Release- wird aufgerufen. Dieser Fehler kann dazu führen, dass Ihr Programm abstürzt. Schlimmer noch, es kann dazu führen, dass Ihr Programm zu einem zufälligen späteren Zeitpunkt abstürzt, wodurch es schwierig ist, den ursprünglichen Fehler nachzuverfolgen.

Eine Möglichkeit, diese Fehler zu vermeiden, besteht darin, Release- über eine Funktion aufzurufen, die den Zeiger sicher loslässt. 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 folgende Aktionen 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 von 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);
}

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

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

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

Intelligente COM-Zeiger

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

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

Als C++-Programmierer denken Sie wahrscheinlich, dass Sie sich nicht an eines dieser Dinge erinnern müssen. Aus diesem Grund verfügt C++ ü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 loslässt. 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 Zuordnungsoperator 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 (ATL) bereitstellt.

Die ATL-Klasse für intelligente Zeiger wird CComPtr-benannt. (Es gibt auch eine CComQIPtr Klasse, die hier nicht erläutert wird.) Hier sehen Sie das Dialogfeld öffnen Beispiel neu geschrieben, um CComPtr-zu verwenden.

#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 Hauptunterschied zwischen diesem Code und dem ursprünglichen Beispiel besteht darin, dass diese Version nicht explizit Releaseaufruft. Wenn die CComPtr- instanz außerhalb des Gültigkeitsbereichs liegt, 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 ausschließlich als Komfort bereitgestellt; Sie können die COM-CoCreateInstance--Funktion weiterhin aufrufen, wenn Sie dies bevorzugen.

Nächster

Fehlerbehandlung in COM-