Das App-Objekt und DirectX
Universelle Windows-Plattform (UWP) mit DirectX-Spielen verwenden nicht viele der Benutzeroberflächenelemente und -objekte der Windows-Benutzeroberfläche. Da sie vielmehr auf einer niedrigeren Ebene im Windows-Runtime-Stapel ausgeführt werden, müssen sie auf grundlegendere Weise mit dem Benutzeroberflächenframework zusammenarbeiten: durch den direkten Zugriff und die Interoperabilität mit dem App-Objekt. Erfahren Sie, wann und wie diese Interoperabilität auftritt und wie Sie als DirectX-Entwickler dieses Modell effektiv bei der Entwicklung Ihrer UWP-App verwenden können.
Im Glossar zu Direct3D-Grafiken finden Sie Informationen zu unbekannten Grafikbegriffen oder Konzepten, die beim Lesen auftreten.
Die wichtigen Kernnamespaces der Benutzeroberfläche
Als Erstes beachten wir die Windows-Runtime Namespaces, die Sie in Ihre UWP-App einschließen müssen (mit Verwendung). Wir kommen in ein bisschen in die Details.
- Windows.ApplicationModel.Core
- Windows.ApplicationModel.Activation
- Windows.UI.Core
- Windows.System
- Windows.Foundation
Hinweis
Wenn Sie keine UWP-App entwickeln, verwenden Sie die in den JavaScript- oder XAML-spezifischen Bibliotheken und Namespaces bereitgestellten Benutzeroberflächenkomponenten anstelle der typen, die in diesen Namespaces bereitgestellt werden.
Das Windows-Runtime App-Objekt
In Ihrer UWP-App möchten Sie ein Fenster und einen Ansichtsanbieter abrufen, aus dem Sie eine Ansicht abrufen können und mit der Sie Ihre Swapchain (Ihre Anzeigepuffer) verbinden können. Sie können diese Ansicht auch in die fensterspezifischen Ereignisse für Ihre ausgeführte App integrieren. Um das übergeordnete Fenster für das App-Objekt abzurufen, das vom CoreWindow-Typ definiert wird, erstellen Sie einen Typ, der IFrameworkViewSource implementiert. Ein C++/WinRT-Codebeispiel zur Implementierung von IFrameworkViewSource finden Sie unter Composition native Interoperabilität mit DirectX und Direct2D.
Im Folgenden finden Sie die grundlegenden Schritte zum Abrufen eines Fensters mithilfe des grundlegenden Benutzeroberflächenframeworks.
Erstellen Sie einen Typ, der IFrameworkView implementiert. Dies ist Ihre Ansicht.
Definieren Sie in diesem Typ Folgendes:
- Eine Initialize-Methode, die eine Instanz von CoreApplicationView als Parameter verwendet. Sie können eine Instanz dieses Typs abrufen, indem Sie CoreApplication.CreateNewView aufrufen. Das App-Objekt ruft sie beim Starten der App auf.
- Eine SetWindow-Methode, die eine Instanz von CoreWindow als Parameter verwendet. Sie können eine Instanz dieses Typs abrufen, indem Sie auf die CoreWindow-Eigenschaft in Ihrer neuen CoreApplicationView-Instanz zugreifen.
- Eine Load-Methode , die eine Zeichenfolge für einen Einstiegspunkt als einzigen Parameter verwendet. Das App-Objekt stellt die Einstiegspunktzeichenfolge bereit, wenn Sie diese Methode aufrufen. Hier richten Sie Ressourcen ein. Hier erstellen Sie Ihre Geräteressourcen. Das App-Objekt ruft sie beim Starten der App auf.
- Eine Run-Methode , die das CoreWindow-Objekt aktiviert und den Fensterereignisteiler startet. Das App-Objekt ruft es auf, wenn der Prozess der App gestartet wird.
- Eine Uninitialize-Methode, die die im Aufruf von Load eingerichteten Ressourcen bereinigt. Das App-Objekt ruft diese Methode auf, wenn die App geschlossen wird.
Erstellen Sie einen Typ, der IFrameworkViewSource implementiert. Dies ist Ihr Ansichtsanbieter.
Definieren Sie in diesem Typ Folgendes:
- Eine Methode mit dem Namen CreateView, die eine Instanz Ihrer IFrameworkView-Implementierung zurückgibt, wie in Schritt 1 erstellt.
Übergeben Sie eine Instanz des Ansichtsanbieters an "CoreApplication.Run" aus dem Hauptteil.
Betrachten wir mit diesen Grundlagen weitere Optionen, die Sie zum Erweitern dieses Ansatzes haben.
Kernbenutzeroberflächentypen
Im folgenden finden Sie weitere wichtige Benutzeroberflächentypen in der Windows-Runtime, die Sie möglicherweise hilfreich finden:
- Windows.ApplicationModel.Core.CoreApplicationView
- Windows.UI.Core.CoreWindow
- Windows.UI.Core.CoreDispatcher
Sie können diese Typen verwenden, um auf die Ansicht Ihrer App zuzugreifen, insbesondere die Bits, die den Inhalt des übergeordneten Fensters der App zeichnen, und die für dieses Fenster ausgelösten Ereignisse behandeln. Der Prozess des App-Fensters ist ein Einzelthread-Apartment (ASTA), das isoliert ist und alle Rückrufe verarbeitet.
Die Ansicht Ihrer App wird vom Ansichtsanbieter für Ihr App-Fenster generiert und in den meisten Fällen durch ein bestimmtes Frameworkpaket oder das System selbst implementiert, sodass Sie sie nicht selbst implementieren müssen. Für DirectX müssen Sie einen Thin View-Anbieter implementieren, wie zuvor beschrieben. Es gibt eine bestimmte 1:1-Beziehung zwischen den folgenden Komponenten und Verhaltensweisen:
- Die Ansicht einer App, die durch den CoreApplicationView-Typ dargestellt wird und die Die Methoden zum Aktualisieren des Fensters definiert.
- Ein ASTA, dessen Zuordnung das Threadingverhalten der App definiert. Sie können keine Instanzen von COM STA-Attribut-Typen für ein ASTA erstellen.
- Ein Ansichtsanbieter, den Ihre App vom System abruft oder den Sie implementieren.
- Ein übergeordnetes Fenster, das durch den CoreWindow-Typ dargestellt wird.
- Beschaffung für alle Aktivierungsereignisse. Sowohl Ansichten als auch Fenster weisen separate Aktivierungsereignisse auf.
Zusammenfassend stellt das App-Objekt eine Ansichtsanbieterfactory bereit. Er erstellt einen Ansichtsanbieter und instanziiert ein übergeordnetes Fenster für die App. Der Ansichtsanbieter definiert die Ansicht der App für das übergeordnete Fenster der App. Jetzt besprechen wir die Besonderheiten der Ansicht und des übergeordneten Fensters.
CoreApplicationView-Verhalten und -Eigenschaften
CoreApplicationView stellt die aktuelle App-Ansicht dar. Der App-Singleton erstellt die App-Ansicht während der Initialisierung, aber die Ansicht bleibt ruhend, bis sie aktiviert wird. Sie können den CoreWindow abrufen, der die Ansicht anzeigt, indem Sie auf die CoreApplicationView.CoreWindow-Eigenschaft darauf zugreifen, und Sie können Aktivierungs- und Deaktivierungsereignisse für die Ansicht behandeln, indem Sie Delegaten beim CoreApplicationView.Activated-Ereignis registrieren.
CoreWindow-Verhalten und -Eigenschaften
Das übergeordnete Fenster, bei dem es sich um eine CoreWindow-Instanz handelt, wird erstellt und an den Ansichtsanbieter übergeben, wenn das App-Objekt initialisiert wird. Wenn die App über ein anzuzeigenes Fenster verfügt, wird es angezeigt; andernfalls initialisiert sie einfach die Ansicht.
CoreWindow stellt eine Reihe von Ereignissen bereit, die für Eingabe- und grundlegende Fensterverhalten spezifisch sind. Sie können diese Ereignisse behandeln, indem Sie Ihre eigenen Stellvertretungen für sie registrieren.
Sie können auch den Fensterereignisverteiler für das Fenster abrufen, indem Sie auf die CoreWindow.Dispatcher-Eigenschaft zugreifen, die eine Instanz von CoreDispatcher bereitstellt.
CoreDispatcher-Verhalten und -Eigenschaften
Sie können das Threadingverhalten der Ereignisverteilung für ein Fenster mit dem CoreDispatcher-Typ bestimmen. Bei diesem Typ gibt es eine besonders wichtige Methode: die CoreDispatcher.ProcessEvents-Methode, die die Verarbeitung von Fensterereignissen startet. Das Aufrufen dieser Methode mit der falschen Option für Ihre App kann zu allen Möglichen unerwarteter Ereignisverarbeitungsverhalten führen.
CoreProcessEventsOption-Option | Beschreibung |
---|---|
CoreProcessEventsOption.ProcessOneAndAllPending | Verteilen Sie alle derzeit verfügbaren Ereignisse in der Warteschlange. Wenn keine Ereignisse ausstehen, warten Sie auf das nächste neue Ereignis. |
CoreProcessEventsOption.ProcessOneIfPresent | Verteilen Sie ein Ereignis, wenn es in der Warteschlange aussteht. Wenn keine Ereignisse ausstehen, warten Sie nicht, bis ein neues Ereignis ausgelöst wird, sondern kehren Sie stattdessen sofort zurück. |
CoreProcessEventsOption.ProcessUntilQuit | Warten Sie auf neue Ereignisse, und verteilen Sie alle verfügbaren Ereignisse. Setzen Sie dieses Verhalten fort, bis das Fenster geschlossen ist oder die Anwendung die Close-Methode für die CoreWindow-Instanz aufruft. |
CoreProcessEventsOption.ProcessAllIfPresent | Verteilen Sie alle derzeit verfügbaren Ereignisse in der Warteschlange. Wenn keine Ereignisse ausstehen, kehren Sie sofort zurück. |
UWP mit DirectX sollte die Option "CoreProcessEventsOption.ProcessAllIfPresent " verwenden, um das Blockieren von Verhaltensweisen zu verhindern, die Grafikaktualisierungen unterbrechen können.
ASTA-Überlegungen für DirectX-Entwickler
Das App-Objekt, das die Laufzeitdarstellung IhrerUWP- und DirectX-App definiert, verwendet ein Threadingmodell namens Application Single-Threaded Apartment (ASTA), um die UI-Ansichten Ihrer App zu hosten. Wenn Sie eine UWP- und DirectX-App entwickeln, sind Sie mit den Eigenschaften eines ASTA vertraut, da jeder Thread, den Sie von Ihrer UWP- und DirectX-App verteilen, die Windows::System::Threading-APIs verwenden muss, oder CoreWindow::CoreDispatcher verwenden muss. (Sie können die CoreWindow-Objekt für das ASTA durch Aufrufen von CoreWindow::GetForCurrentThread aus Ihrer App.)
Das Wichtigste, was Sie als Entwickler einer UWP-DirectX-App beachten müssen, besteht darin, dass Sie den App-Thread aktivieren müssen, um MTA-Threads zu verteilen, indem Sie "Platform::MTAThread on main()" festlegen.
[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory
CoreApplication::Run(myDXAppSource);
return 0;
}
Wenn das App-Objekt für Ihre UWP DirectX-App aktiviert wird, wird das ASTA erstellt, das für die UI-Ansicht verwendet wird. Der neue ASTA-Thread ruft die Factory des Ansichtsanbieters auf, um den Ansichtsanbieter für Ihr App-Objekt zu erstellen, und daher wird ihr Ansichtsanbietercode für diesen ASTA-Thread ausgeführt.
Außerdem muss sich jeder Thread, den Sie aus dem ASTA herausdrehen, in einem MTA befinden. Beachten Sie, dass alle MTA-Threads, die Sie spinnen, weiterhin Reentrancy-Probleme erzeugen und zu einem Deadlock führen können.
Wenn Sie vorhandenen Code portieren, der im ASTA-Thread ausgeführt werden soll, beachten Sie die folgenden Überlegungen:
Wait primitives, such as CoWaitForMultipleObjects, verhalten sich in einem ASTA anders als in einem STA.
Die modale COM-Aufrufschleife funktioniert in einem ASTA anders. Sie können keine nicht verbundenen Anrufe mehr empfangen, während ein ausgehender Anruf ausgeführt wird. Das folgende Verhalten erstellt beispielsweise einen Deadlock aus einem ASTA (und stürzt die App sofort ab):
- Das ASTA ruft ein MTA-Objekt auf und übergibt einen Schnittstellenzeiger P1.
- Später ruft das ASTA dasselbe MTA-Objekt auf. Das MTA-Objekt ruft P1 auf, bevor es an das ASTA zurückkehrt.
- P1 kann das ASTA nicht eingeben, da er blockiert wird, wenn er einen nicht verknüpften Aufruf vornimmt. Der MTA-Thread wird jedoch blockiert, da er versucht, den Aufruf von P1 zu tätigen.
Sie können dies beheben, indem Sie:
- Verwenden des asynchronen Musters, das in der Parallel Patterns Library (PPLTasks.h) definiert ist
- Aufrufen von CoreDispatcher::P rocessEvents aus dem ASTA Ihrer App (dem Hauptthread Ihrer App) so schnell wie möglich, um beliebige Aufrufe zuzulassen.
Das heißt, Sie können sich nicht auf die sofortige Übermittlung nicht verwandter Aufrufe an das ASTA Ihrer App verlassen. Weitere Informationen zu asynchronen Aufrufen finden Sie unter "Asynchrone Programmierung in C++".
Verwenden Sie beim Entwerfen Ihrer UWP-App den CoreDispatcher für die CoreWindow- und CoreDispatcher::P rocessEvents-App, um alle UI-Threads zu verarbeiten, anstatt selbst zu versuchen, Ihre MTA-Threads zu erstellen und zu verwalten. Wenn Sie einen separaten Thread benötigen, den Sie nicht mit CoreDispatcher behandeln können, verwenden Sie asynchrone Muster, und befolgen Sie die oben erwähnten Anleitungen, um Reentranzprobleme zu vermeiden.