Windows mit C++

Windows-Runtime-Anwendungsmodell

Kenny Kerr

Kenny Kerr
In unserem Leben wimmelt es nur so von Abstraktionen. Als Entwickler bekommen wir oft Probleme, wenn wir Abstraktionen verwenden, ohne diese genau zu verstehen. Manchmal sind Abstraktionen beschädigt und verbergen die zugrundeliegende Komplexität nicht ganz. Verstehen Sie mich nicht falsch. Abstraktionen sind toll! Sie machen Benutzern und Entwicklern das Leben leichter, aber Sie tun sich einen Gefallen, wenn Sie die Funktionsweise der Abstraktionen, die Sie regelmäßig einsetzen, genauer unter die Lupe nehmen. Außerdem sind Bibliotheken, bei denen diese Tatsache anerkannt wird, oft erfolgreicher als andere. Teilweise liegt dies daran, dass Sie die Abstraktion bei Bedarf abchecken können.

Windows-Runtime (WinRT) ist solch eine Abstraktion, und im Artikel dieses Monats werde ich dies unter Beweis stellen, indem ich das Kernanwendungsmodell WinRT vorstelle. Dieses Modell dreht sich um die CoreWindow-Klasse, von der in jeder „modernen“ Windows Store- und Windows Phone-App eine Instanz vorhanden ist. Dennoch wissen recht wenige Entwickler von dieser Klasse, geschweige denn, wie sie funktioniert. Vielleicht ist die Abstraktion gerade deshalb so erfolgreich.

Seit die Windows 8-API 2011 erstmals angekündigt wurde, ist viel über die verschiedenen Sprachprojektionen, die über Windows-Runtime eine Abstraktion anbieten, gesprochen und geschrieben worden. Windows-Runtime lässt sich jedoch am besten verstehen, wenn Sie die verschiedenen Sprachprojektionen (einschließlich C++/CX) meiden und lieber Standard-C++ und klassisches COM verwenden. Nur mit C++ können Sie den Vorhang aufziehen und sehen, was passiert. Technisch betrachtet ist dies natürlich auch mit C möglich, aber dies wäre unnötig komplex. Vielleicht entscheiden Sie sich dennoch für die eine oder andere Sprachprojektion (hoffentlich für C++/CX), und dies ist wohl auch ratsam, aber immerhin sind Sie jetzt wesentlich besser mit dem Ablauf vertraut.

Öffnen Sie zu Beginn Visual Studio 2012, und erstellen Sie ein neues Visual C++-Projekt für eine Windows Store- oder Windows Phone-App. Es ist unerheblich, welche Vorlage Sie hierfür verwenden. Löschen Sie nach dem Laden im Projektmappen-Explorer alles, was nicht benötigt wird. Wenn Sie eine XAML-basierte Vorlage ausgewählt haben, löschen Sie alle XAML-Dateien. Auch die C++-Quelldateien können Sie löschen. Vielleicht möchten Sie die vorkompilierte Kopfzeile weiter nutzen, löschen Sie aber unbedingt deren Inhalte. Es sollten lediglich die Paketressourcen, die für die App-Bereitstellung erforderlich sind, Bilder, Zertifikat und XML-Manifest übrig sein.

Öffnen Sie dann die Eigenschaftenseiten des Projekts und wählen Sie die Compiler-Eigenschaften aus (C/C++-Knoten links in der Struktur). Suchen Sie die Zeile für die /ZW-Compiler-Option, die die Bezeichnung „Windows-Runtime-Erweiterung verwenden“ trägt, und klicken Sie auf „Nein“, um die C++/CX-Spracherweiterungen zu deaktivieren. So können Sie sicher sein, dass abgesehen von den wunderbaren Geheimnissen des Standard-C++-Compilers keine seltsamen Dinge passieren. Und wenn Sie schon dort sind, können Sie auch gleich noch die Warnstufe des Compilers auf „/W4“ setzen.

Wenn Sie das Projekt kompilieren möchten, sollte ein Linker-Fehler angezeigt werden, durch den Sie darüber informiert werden, dass der WinMain-Haupteinstiegspunkt des Projekts nicht gefunden werden kann. Fügen Sie beim Einfügen einer neuen C++-Quelldatei ins Projekt auch gleich die fehlende WinMain-Funktion hinzu.

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
}

Wie Sie sehen, ist dies die uralte WinMain-Funktion für eine auf C-Laufzeitbibliotheken (CRT) basierende Windows-Anwendung. Selbstverständlich sind HINSTANCE und PWSTR nicht grundsätzlich C++-Typen, weshalb die Windows-Kopfzeile eingefügt werden muss.

#include <windows.h>

Wenn Sie die vorkompilierte Kopfzeile des Projekts nicht gelöscht haben, können Sie diese hier einfügen. Ich werde auch ComPtr aus der C++-Vorlagenbibliothek für Windows-Runtime (WRL) verwenden, und nun wäre ein guter Moment, auch diese hinzuzufügen.

#include <wrl.h>

In den nächsten Artikeln werde ich noch genauer auf WRL eingehen. Momentan nutze ich die ComPtr-Klassenvorlage lediglich wegen des COM-Schnittstellenzeigers. Denken Sie einfach daran, dass WRL-ComPtr ein intelligenter COM-Schnittstellenzeiger ist. Obwohl er bestimmte Features bereitstellt, die für Windows-Runtime einzigartig sind, werde ich im Artikel dieses Monats nicht darauf eingehen. Sie könnten stattdessen auch den CComPtr aus der aktiven Vorlagenbibliothek (ATL) verwenden oder einen beliebigen intelligenten COM-Schnittstellenzeiger Ihrer Wahl. Der WRL-ComPtr ist im Namespace „Microsoft::WRL“ angegeben.

using namespace Microsoft::WRL;

Außerdem werde ich ein ASSERT-Makro und die HR-Funktion zur Fehlerbehandlung verwenden. Da ich dies bereits an anderer Stelle besprochen habe, werde ich nicht näher darauf eingehen. Sollten Sie mit diesen Schritten nicht vertraut sein, können Sie in meinem Artikel von Mai 2013 nachlesen („Einführung in Direct2D 1.1“; msdn.microsoft.com/magazine/dn198239).

Abschließend sollte erwähnt werden, dass bei der Verwendung einer der in diesem Artikel genannten WinRT-Funktionen der Linker den Namen der LIB-Datei tragen muss.

#pragma comment(lib, "RuntimeObject.lib")

An erster Stelle erwartet das Anwendungsmodell ein MTA (Multi-threaded Apartment). Das stimmt. Das COM-Apartment-Modell basiert darauf. Windows-Runtime stellt die RoInitialize-Funktion bereit, bei der es sich um einen einfachen Wrapper für CoInitializeEx handelt.

HR(RoInitialize(RO_INIT_MULTITHREADED));

Obwohl CoInitializeEx gewöhnlich genügt, empfehle ich die Verwendung von RoInitialize. Durch diese Funktion können künftig Verbesserungen von Windows-Runtime vorgenommen werden, ohne Gefahr zu laufen, dass dabei das klassische COM beschädigt wird. Es ist analog zu OleInitialize, von dem ebenfalls CoInitializeEx und dann „some“ aufgerufen werden. Der Hauptthread Ihrer Anwendung birgt keine Geheimnisse. Das einzige, was vielleicht etwas überrascht, ist, dass er kein STA (Single-threaded Apartment) ist. Keine Sorge. Das Anwendungsfenster wird dennoch über einen STA-Thread ausgeführt, allerdings wird dieser von Windows-Runtime erstellt. Dieses STA ist eigentlich ein ASTA (Application STA), das ein paar Unterschiede aufweist, aber mehr dazu später.

 Jetzt wird es ein wenig knifflig. Windows-Runtime gibt das herkömmliche COM-Aktivierungsmodell, von dem GUID-basierte Klassen-IDs verwendet werden, zugunsten eines Modells auf, bei dem Klassen basierend auf Textklassen-IDs aktiviert werden. Die Textnamen basieren auf durch den Namespace begrenzten Klassennamen, die durch Java und das Microsoft .NET-Framework beliebt wurden. Aber bevor Sie sich über die Registrierung lustig machen und sie ganz aufgeben, denken Sie daran, dass auch diese neuen Klassen-IDs im Register gespeichert werden. Aus technischer Sicht werden nur eigene Typen in der Registrierung gespeichert, Typen von Dritten jedoch nur in einem Anwendungs-Manifest. Diese Änderung hat Vor- und Nachteile. Einer der Nachteile ist es, dass es etwas schwieriger ist, die Klassen-ID beim Aufrufen einer WinRT-Funktion zu beschreiben. Windows-Runtime bestimmt einen neuen Remote-Zeichenfolgentyp, der den herkömmlichen BSTR-Zeichenfolgentyp ersetzt, und jede Klassen-ID muss über diesen neuen Typ bereitgestellt werden. Der neue Typ heißt HSTRING und ist wesentlich weniger fehleranfällig als BSTR, besonders weil er unveränderlich ist. Am einfachsten lässt sich ein HSTRING mit der WindowsCreateString-Funktion erstellen.

wchar_t buffer[] = L"Poultry.Hatchery";
HSTRING string;
HR(WindowsCreateString(buffer,
                       _countof(buffer) - 1,
                       &string));

WindowsCreateString stellt ausreichend Speicherplatz bereit, um eine Kopie der Quellzeichenfolge und eines abschließenden Null-Zeichens aufzubewahren und die Quellzeichenfolge dann in diesen Puffer zu kopieren. Denken Sie bei der Freigabe des unterstützenden Puffers daran, WindowsDeleteString aufzurufen, sofern das Eigentum an der Zeichenfolge nicht an eine Aufruffunktion zurückgegeben wurde.

HR(WindowsDeleteString(string));

Über einen HSTRING können Sie mit der WindowsGetStringRawBuffer-Funktion einen Zeiger zum unterstützenden Puffer erhalten.

wchar_t const * raw = WindowsGetStringRawBuffer(string, nullptr);

Der optionale zweite Parameter gibt die Länge der Zeichenfolge zurück. Die Länge wird zusammen mit der Zeichenfolge gespeichert, wodurch es Ihnen erspart bleibt, die Zeichenfolge zur Längenbestimmung zu überprüfen. Bevor Sie sich aufmachen, um eine C++-Wrapperklasse zu schreiben, sollten Sie daran denken, dass der C++/CX-Compiler weder WindowsCreateString noch WindowsDelete­String verwendet, wenn Code für ein Zeichenfolgenliteral oder const-Array generiert wird. Stattdessen wird eine schnell durchlaufende Zeichenfolge verwendet, bei der zusätzliche Speicherzuweisung und die oben erwähnte Kopie vermieden werden. Auch wird dadurch das Risiko eines Speicherverlusts umgangen. Die WindowsCreateStringReference-Funktion erstellt eine schnell durchlaufende Zeichenfolge.

HSTRING_HEADER header;
HSTRING string;
HR(WindowsCreateStringReference(buffer,
                                _countof(buffer) - 1,
                                &header,
                                &string));

Diese Funktion verwendet den vom Aufrufer bereitgestellten HSTRING_HEADER zur Vermeidung einer Heapzuweisung. In diesem Fall ist der unterstützende Puffer für den HSTRING die Quellzeichenfolge selbst. Achten Sie daher darauf, dass die Quellzeichenfolge (und die Kopfzeile) so lange unverändert bleibt, wie der HSTRING gültig ist. Diese Vorgehensweise eignet sich offensichtlich nicht, wenn Sie eine Zeichenfolge an eine Aufruffunktion zurückgeben müssen. Die Optimierung ist jedoch sinnvoll, wenn Sie eine Zeichenfolge als Eingabe an eine andere Funktion weitergeben müssen, deren Gültigkeitsdauer durch den Stapel begrenzt ist (keine asynchronen Funktionen). WRL bietet auch Wrapper für HSTRING und schnell durchlaufende Zeichenfolgen.

RoGetActivationFactory ist solch eine Funktion und wird verwendet, um die Aktivierungsfactory oder statische Schnittstelle für eine bestimmte Klasse zu ermitteln. Dies entspricht der COM-Funktion „CoGetClassObject“. Angesichts der Tatsache, dass diese Funktion gewöhnlich mit einem const-Array verwendet wird, das der MIDL-Compiler generiert, bietet sich die Erstellung einer einfachen Funktionsvorlage an, damit ein schnell durchlaufender Zeichenfolgenwrapper zur Verfügung steht. Aus Abbildung 1 geht hervor, wie diese Vorlage aussehen könnte.

Abbildung 1: Vorlage für die GetActivationFactory-Funktion

template <typename T, unsigned Count>
auto GetActivationFactory(WCHAR const (&classId)[Count]) -> ComPtr<T>
{
  HSTRING_HEADER header;
  HSTRING string;
  HR(WindowsCreateStringReference(classId,
                                  Count - 1,
                                  &header,
                                  &string));
  ComPtr<T> result;
  HR(RoGetActivationFactory(string,
    __uuidof(T),
    reinterpret_cast<void **>(result.GetAddressOf())));
  return result;
}

Die Vorlage für die GetActivationFactory-Funktion leitet die Zeichenfolgenlänge automatisch ab, wodurch ein fehleranfälliges Pufferlängenargument oder kostspielige Runtime-Prüfungen vermieden werden. Dann wird eine schnell durchlaufende Zeichenfolge vorbereitet, bevor die eigentliche RoGetActivationFactory-Funktion aufgerufen wird. Auch hier leitet die Funktionsvorlage den Schnittstellenbezeichner ab und gibt den resultierenden COM-Schnittstellenzeiger mit WRL-ComPtr-Wrapper zurück.

Jetzt können Sie mit dieser Hilfsfunktion die ICoreApplication-Schnittstelle erhalten.

using namespace ABI::Windows::ApplicationModel::Core;
auto app = GetActivationFactory<ICoreApplication>(
  RuntimeClass_Windows_ApplicationModel_Core_CoreApplication);

Die ICoreApplication-Schnittstelle bringt den Ball für Ihre Anwendung ins Rollen. Zur Nutzung dieser COM-Schnittstelle muss die Modellkopfzeile der Anwendung eingefügt sein.

#include <Windows.ApplicationModel.Core.h>

Durch diese Kopfzeile werden ICoreApplication innerhalb des Namespace „ABI::Windows::ApplicationModel::Core“ sowie die Textklassen-ID von Core­Application bestimmt. Die einzige Schnittstellenmethode, an die Sie wirklich denken müssen, ist die Run-Methode.

Bevor ich weitermache, sollten Sie sich überleben, wie Windows-Runtime Ihrer Anwendung Leben einhaucht. Ich habe ja bereits erwähnt, dass Windows-Runtime Sie nur als Gast innerhalb Ihres eigenen Prozesses betrachtet. Dies ist damit vergleichbar, wie Windows-Dienste jahrelang funktioniert haben. Im Falle eines Windows-Dienstes startet der Dienststeuerungs-Manager von Windows (SCM) den Dienst über die CreateProcess-Funktion oder eine ihrer Varianten. Dann wird gewartet, bis vom Prozess die StartServiceCtrlDispatcher-Funktion aufgerufen wird. Über diese Funktion wird eine Verbindung zurück zum SCM hergestellt, über die Dienst und SCM kommunizieren können. Wenn der Dienst beispielsweise nicht rechtzeitig StartService­CtrlDispatcher aufruft, geht der SCM davon aus, dass etwas schiefgelaufen ist, und er bricht den Prozess ab. Die StartServiceCtrl­Dispatcher-Funktion wird erst zurückgegeben, wenn der Dienst beendet ist. Die SCM muss daher einen sekundären Thread erstellen, damit der Dienst Rückrufbenachrichtigungen erhält. Der Dienst reagiert lediglich auf Ereignisse und hängt daher von SCM ab. Sie werden feststellen, dass dies dem WinRT-Anwendungsmodell sehr ähnlich ist.

Windows-Runtime wartet, bis der Prozess über die ICore­Application-Schnittstelle verfügt und die Run-Methode aufruft. Wie beim SCM geht Windows-Runtime davon aus, dass etwas schiefgelaufen ist, und bricht den Prozess ab, wenn der Prozess diesen Schritt nicht rechtzeitig ausgeführt hat. Ist ein Debugger eingebunden, erkennt Windows-Runtime glücklicherweise die Zeitüberschreitung und deaktiviert sie. Beim SCM ist dies nicht der Fall. Beim Modell gibt es jedoch keinen Unterschied. Windows-Runtime übernimmt die Verantwortung und ruft die Anwendung auf einem von Runtime erstellten Thread auf, wenn ein Ereignis eintritt. Natürlich basiert Windows-Runtime auf COM. Anstatt eine Rückruffunktion zu haben, wie beim SCM, geht Windows Runtime, das vom Process Lifetime Manager (PLM) abhängt, davon aus, dass die Anwendung die Run-Methode über eine COM-Schnittstelle bereitstellt und es diese dann zum Aufrufen der Anwendung verwenden kann.

Ihre App muss eine Implementierung von IFramework­ViewSource bereitstellen, das ebenfalls vom Namespace „ABI::Windows::ApplicationModel::Core“ stammt. CoreApplication ruft die einzige CreateView-Methode auf, sobald es den UI-Thread Ihrer App erstellt hat. IFrameworkViewSource verfügt aber nicht nur über die Methode „CreateView“. Es stammt von IInspectable, der WinRT-Basisschnittstelle. IInspectable wiederum stammt von IUnknown, der COM-Basisschnittstelle.

WRL bietet beim Implementieren von COM-Klassen umfassende Unterstützung, aber dieses Thema hebe ich mir für einen künftigen Artikel auf. Jetzt möchte ich besonders hervorheben, wie sehr Windows-Runtime in COM verwurzelt ist. Und wie könnte ich dies besser tun, als durch die Implementierung von IUnknown? Für meine Zwecke möchte ich darauf hinweisen, dass die Gültigkeitsdauer der C++-Klasse, von der IFrameworkViewSource implementiert wird, und einiger anderer Klassen durch den Stapel bestimmt wird. Die WinMain-Funktion der Anwendung lässt sich folgendermaßen zusammenfassen:

HR(RoInitialize(RO_INIT_MULTITHREADED));
auto app = GetActivationFactory<ICoreApplication>( ...
SampleWindow window;
HR(app->Run(&window));

Jetzt muss nur noch die SampleWindow-Klasse geschrieben werden, damit sie IFrameworkViewSource richtig implementiert. Obwohl es CoreApplication egal ist, wo implementiert wird, muss Ihre App mindestens IFrameworkViewSource sowie die Schnittstellen „IFrameworkView“ und „IActivatedEventHandler“ implementieren. In diesem Fall kann die SampleWindow-Klasse einfach alle implementieren.

struct SampleWindow :
  IFrameworkViewSource,
  IFrameworkView,
  IActivatedEventHandler
{};

Die IFrameworkView-Schnittstelle wird auch im Namespace „ABI::Windows::ApplicationModel::Core“ festgelegt, IActivatedEvent­Handler ist aber etwas schwerer zu finden. Ich habe es folgendermaßen definiert:

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel::Activation;
typedef ITypedEventHandler<CoreApplicationView *, 
  IActivatedEventArgs *>
  IActivatedEventHandler;

Wenn Sie sich etwas mit COM auskennen, kommt Ihnen dies vielleicht etwas ungewöhnlich vor. Sie haben recht. Wie erwartet ist „ITypedEventHandler“ nur eine Klassenvorlage, und dies ist eine recht seltsame Art, eine COM-Schnittstelle zu definieren. Das offensichtlichste Problem ist, dass nicht ersichtlich ist, welchem Schnittstellenbezeichner es zugewiesen werden soll. Glücklicherweise werden all diese Schnittstellen vom MIDL-Compiler erstellt. Er kümmert sich darum, jede einzelne zu spezialisieren und bei dieser Spezialisierung wird die GUID eingebunden, die für den Schnittstellenbezeichner steht. Die letzte Typdefinition mag zwar etwas kompliziert erscheinen, doch definiert sie eine COM-Schnittstelle, die direkt von IUnknown stammt und eine einzige Methode, „Invoke“, bereitstellt.

Ich muss ein paar Schnittstellenmethoden implementieren. Lassen Sie uns also gleich anfangen. Zuerst kommen IUnknown und die leistungsstarke QueryInterface-Methode. Ich möchte hier aber nicht zu viel Zeit mit IUnknown und IInspectable verbringen, da sie in einem bald erscheinenden Artikel genauer behandelt werden. In Abbildung 2 sehen Sie eine einfache Implementierung von QueryInterface für eine stapelbasierte Klasse wie diese.

Abbildung 2: SampleWindow-QueryInterface-Methode

auto __stdcall QueryInterface(IID const & id,
                              void ** result) -> HRESULT
{
  ASSERT(result);
  if (id == __uuidof(IFrameworkViewSource) ||
      id == __uuidof(IInspectable) ||
      id == __uuidof(IUnknown))
  {
    *result = static_cast<IFrameworkViewSource *>(this);
  }
  else if (id == __uuidof(IFrameworkView))
  {
    *result = static_cast<IFrameworkView *>(this);
  }
  else if (id == __uuidof(IActivatedEventHandler))
  {
    *result = static_cast<IActivatedEventHandler *>(this);
  }
  else
  {
    *result = nullptr;
    return E_NOINTERFACE;
  }
  // static_cast<IUnknown *>(*result)->AddRef();
  return S_OK;
}

Es sollte auf einige Aspekte dieser Implementierung hingewiesen werden. An erster Stelle wird durch die Methode bestätigt, dass ihre Argumente gültig sind. Bei einer politisch korrekteren Implementierung würde vielleicht E_POINTER ausgegeben, aber es wird davon ausgegangen, dass diese Fehler bei der Entwicklung behoben werden können. Es muss daher nicht während der Laufzeit Zeit für weitere Zyklen verschwendet werden. So entsteht das bestmögliche Verhalten, indem sofort eine Zugriffsverletzung und ein Absturzbild, das sich ziemlich leicht analysieren lässt, verursacht werden. Wenn Sie E_POINTER zurückgeben, wird der beschädigte Aufrufer dies wahrscheinlich einfach ignorieren. Am besten tritt frühzeitig ein Problem auf. Diese Haltung wird von vielen Implementierungen vertreten, auch bei DirectX und Windows-Runtime. Es ist aufwändig, QueryInterface richtig zu implementieren. Die COM-Spezifikation ist recht spezifisch, damit COM-Klassen stets bestimmte Objektidentitätsgarantien richtig und einheitlich geben. Lassen Sie sich von der Reihe von IF-Anweisungen nicht einschüchtern. Ich werde darüber noch im Detail sprechen.

Abschließend sollte zu dieser Implementierung noch gesagt werden, dass sie AddRef gar nicht aufruft. Normalerweise muss QueryInterface vor der Rückgabe AddRef über den resultierenden IUnknown-Schnittstellenzeiger aufrufen. Da sich die SampleWindow-Klasse jedoch auf dem Stapel befindet, bringt die Verweiszählung nichts. Aus demselben Grund fällt die Implementierung der Methoden „IUnknown AddRef“ und „Release“ leicht.

auto __stdcall AddRef()  -> ULONG { return 2; }
auto __stdcall Release() -> ULONG { return 1; }

Die Ergebnisse dieser Methoden dienen lediglich der Beratung. Sie können dies also nutzen, und ein beliebiger Wert mit Ausnahme von Null funktioniert. Ein Wort der Warnung: Sie können Operatoren überschreiben, um deutlich zu machen, dass die Klasse nur für den Einsatz im Stapel vorgesehen ist. Alternativ könnten Sie einfach die Verweiszählung implementieren. Nur als Ergänzung.

Als nächstes muss ich IInspectable implementieren. Da es für diese einfache Anwendung aber nicht zum Einsatz kommen wird, schummle ich etwas und lasse seine Methoden unimplementiert (siehe Abbildung 3). Dies ist keine entsprechende Implementierung, und möglicherweise funktioniert sie nicht. Auf IInspectable werde ich ja im nächsten Artikel eingehen, aber dies genügt, um die von SampleWindow IInspectable abgeleiteten Schnittstellen zum Laufen zu bringen.

Abbildung 3: SampleWindow-IInspectable-Methoden

auto __stdcall GetIids(ULONG *,
                       IID **) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetRuntimeClassName(HSTRING *) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetTrustLevel(TrustLevel *) -> HRESULT
{
  return E_NOTIMPL;
}

Jetzt müssen IFrameworkViewSource und die CreateView-Methode implementiert werden. Da IFrameworkView auch von der SampleWindow-Klasse implementiert wird, ist die Implementierung einfach. Und nochmal als Erinnerung: Normalerweise muss AddRef vor der Rückgabe über den resultierenden, von IUnknown abgeleiteten Schnittstellenzeiger aufgerufen werden. Vielleicht möchten Sie AddRef im Textkörper dieser Funktion aufrufen.

auto __stdcall CreateView(IFrameworkView ** result) -> HRESULT
{
  ASSERT(result);
  *result = this;
  // (*result)->AddRef();
  return S_OK;
}

Die IFrameworkView-Schnittstelle ist der Ort, an dem es für die Anwendung langsam interessant wird. Nachdem „CreateView“ aufgerufen wurde, um den Schnittstellenzeiger aus der Anwendung zu ermitteln, ruft Windows-Runtime die meisten seiner Methoden kurz hintereinander auf. Es ist wichtig, dass Sie auf diese Aufrufe schnell reagieren, denn sie zählen als Zeit, die der Benutzer darauf wartet, dass Ihre Anwendung startet. Der erste heißt Initialize. An dieser Stelle muss die Anwendung für das Activated-Ereignis registriert werden. Das Activated-Ereignis signalisiert, dass die Anwendung aktiviert wurde, die Aktivierung von CoreWindow hängt jedoch von der Anwendung selbst ab. Die Methode „Initialize“ ist recht einfach.

auto __stdcall Initialize(ICoreApplicationView * view) -> HRESULT
{
  EventRegistrationToken token;
  HR(view->add_Activated(this, &token));
  return S_OK;
}

Die SetWindow-Methode wird daraufhin aufgerufen und ermöglicht der Anwendung die eigentliche ICoreWindow-Implementierung. Ein ICoreWindow modelliert lediglich ein reguläres Desktop-HWND in Windows-Runtime. Im Gegensatz zu den bisherigen Anwendungsmodellschnittstellen wird ICoreWindow im Namespace „ABI::Windows::UI::Core“ definiert. Bei der SetWindow-Methode sollten Sie lediglich eine Kopie des Schnittstellenzeigers anlegen, da Sie diese bald benötigen werden.

using namespace ABI::Windows::UI::Core;
ComPtr<ICoreWindow> m_window;
auto __stdcall SetWindow(ICoreWindow * window) -> HRESULT
{
  m_window = window;
  return S_OK;
}

Jetzt kommt die Load-Methode. Hier sollten Sie den ganzen Code einfügen, um die Anwendung für die erste Präsentation vorzubereiten.

auto __stdcall Load(HSTRING) -> HRESULT
{
  return S_OK;
}

Mindestens sollten Sie für Ereignisse eine Registrierung vornehmen, die sich auf die Fenstergröße und auf Sichtbarkeitsänderungen, aber auch auf Änderungen der DPI-Skalierung beziehen. Sie können auch die Gelegenheit nutzen, um zum Beispiel die verschiedenen DirectX-Factoryobjekte zu erstellen und geräteunabhängige Ressourcen zu laden. Dies ist deshalb ein guter Zeitpunkt, weil dem Benutzer jetzt der Begrüßungsbildschirm angezeigt wird.

Wenn die Load-Methode zurückgegeben wird, geht Windows-Runtime davon aus, dass Ihre Anwendung aktiviert werden kann, und startet das Activated-Ereignis, auf das ich durch die Implementierung der IActivatedEventHandler Invoke-Methode reagiere. Etwa so:

auto __stdcall Invoke(ICoreApplicationView *,
                      IActivatedEventArgs *) -> HRESULT
{
  HR(m_window->Activate());
  return S_OK;
}

Bei aktiviertem Fenster kann die Anwendung endlich ausgeführt werden.

auto __stdcall Run() -> HRESULT
{
  ComPtr<ICoreDispatcher> dispatcher;
  HR(m_window->get_Dispatcher(dispatcher.GetAddressOf()));
  HR(dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessUntilQuit));
  return S_OK;
}

Diese Implementierung kann auf vielerlei Weise stattfinden. Hier rufe ich lediglich die ICoreDispatcher-Schnittstelle des Fensters ab, die Meldungsverschiebung für das Fenster. Und nun zu guter Letzt die Uninitialize-Methode, die vielleicht ab und an aufgerufen wird, sonst aber nutzlos ist und problemlos ignoriert werden kann.

auto __stdcall Uninitialize() -> HRESULT
{
  return S_OK;
}

Und das wär's! Sie können die Anwendung jetzt kompilieren und ausführen. Gemalt werden muss hier natürlich nichts. Holen Sie sich unter dx.codeplex.com eine Kopie von dx.h, und beginnen Sie mit dem Hinzufügen von Direct2D-Renderingcode. Weitere Informationen hierzu finden Sie unter msdn.microsoft.com/magazine/dn201741 in meinem Artikel von Juni 2013 („Eine moderne C++-Bibliothek für die DirectX-Programmierung“). Sie können aber auch auf meinen nächsten Artikel warten, in dem Sie erfahren werden, wie Sie Direct2D am besten in das WinRT-Kernanwendungsmodell integrieren.

Kenny Kerrist ein Programmierer aus Kanada, Autor bei Pluralsight und ein Microsoft MVP. Er veröffentlicht Blogs unter kennykerr.ca, und Sie können ihm auf Twitter unter twitter.com/kennykerr folgen.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: James McNellis (Microsoft)
James McNellis ist ein begeisterter Anhänger von C++ und Softwareentwickler im Visual C++-Team bei Microsoft, wo er herausragende C- und C++-Bibliotheken erstellt. Er twittert unter @JamesMcNellis und kann online auch über http://jamesmcnellis.com/ erreicht werden.