Share via


Oggetto app e DirectX

Universal Windows Platform (UWP) con giochi DirectX non usa molti degli elementi e degli oggetti dell'interfaccia utente di Windows. Invece, poiché vengono eseguiti a un livello inferiore nello stack di Windows Runtime, devono interagire con il framework dell'interfaccia utente in modo più fondamentale: accedendo e interoperando direttamente con l'oggetto app. Scopri quando e come si verifica questa interoperabilità e come, come sviluppatore DirectX, puoi usare efficacemente questo modello nello sviluppo della tua app UWP.

Vedere il glossario della grafica Direct3D per informazioni su termini o concetti grafici sconosciuti incontrati durante la lettura.

Spazi dei nomi principali dell'interfaccia utente

Prima di tutto, annotiamo gli spazi dei nomi di Windows Runtime da includere (con using) nella tua app UWP. Entriamo un po' nei dettagli.

Nota

Se non stai sviluppando un'app UWP, usa i componenti dell'interfaccia utente forniti nelle librerie e negli spazi dei nomi specifici di JavaScript o XAML anziché i tipi forniti in questi spazi dei nomi.

Oggetto app di Windows Runtime

Nella tua app UWP vuoi ottenere una finestra e un provider di visualizzazioni da cui puoi ottenere una vista e a cui puoi connettere la catena di scambio (buffer di visualizzazione). È anche possibile associare questa vista agli eventi specifici della finestra per l'app in esecuzione. Per ottenere la finestra padre per l'oggetto app, definita dal tipo CoreWindow, crea un tipo che implementi IFrameworkViewSource. Per un esempio di codice C++/WinRT che mostra come implementare IFrameworkViewSource, consulta Interoperabilità nativa della composizione con DirectX e Direct2D.

Ecco la serie base di passaggi per ottenere una finestra usando il framework dell'interfaccia utente principale.

  1. Crea un tipo che implementa IFrameworkView. Questa è la tua visualizzazione.

    In questo tipo, definisci:

    • Un metodo Initialize che accetti un'istanza di CoreApplicationView come parametro. È possibile ottenere un'istanza di questo tipo chiamando CoreApplication.CreateNewView. L'oggetto app lo chiama all'avvio dell'app.
    • Un metodo SetWindow che accetti un'istanza di CoreWindow come parametro. È possibile ottenere un'istanza di questo tipo accedendo alla proprietà CoreWindow nella nuova istanza CoreApplicationView.
    • Un metodo Load che accetti una stringa per un punto di ingresso come unico parametro. L'oggetto app fornisce la stringa del punto di ingresso quando si chiama questo metodo. A questo punto è possibile configurare le risorse. Le risorse del dispositivo vengono create qui. L'oggetto app lo chiama all'avvio dell'app.
    • Un metodo Run che attivi l'oggetto CoreWindow e avvii il dispatcher eventi della finestra. L'oggetto app lo chiama all'avvio del processo dell'app.
    • Un metodo Uninitialize che rimuova le risorse configurate nella chiamata a Load. L'oggetto app chiama questo metodo quando l'app viene chiusa.
  2. Crea un tipo che implementa IFrameworkViewSource. Questo è il provider di visualizzazioni.

    In questo tipo, definisci:

    • Un metodo denominato CreateView che restituisca un'istanza dell'implementazione IFrameworkView come creata nel passaggio 1.
  3. Passa un'istanza del provider di visualizzazione a CoreApplication.Run da main.

Tenendo presenti queste nozioni di base, esaminiamo altre opzioni per ampliare questo approccio.

Tipi di interfaccia utente di base

Ecco altri tipi di interfaccia utente di base in Windows Runtime che potrebbero risultare utili:

Puoi usare questi tipi per accedere alla visualizzazione dell'app, in particolare i bit che disegnano il contenuto della finestra padre dell'app e gestiscono gli eventi generati per tale finestra. Il processo della finestra dell'app è un'applicazione di tipo apartment a thread singolo (ASTA) isolata e che gestisce tutte le richiamate.

La visualizzazione dell'app viene generata dal provider di visualizzazioni per la finestra dell'app e, nella maggior parte dei casi, verrà implementata da un pacchetto framework specifico o dal sistema stesso, per cui non è necessaria alcuna implementazione manuale. Per DirectX, è necessario implementare un provider di thin view, come illustrato in precedenza. Esiste una relazione specifica 1 a 1 tra i componenti e i comportamenti seguenti:

  • Visualizzazione di un'app, rappresentata dal tipo CoreApplicationView, che definisce i metodi per l'aggiornamento della finestra.
  • ASTA, la cui attribuzione definisce il comportamento di threading dell'app. Non è possibile creare istanze di tipi con attributi COM STA in una ASTA.
  • Provider di visualizzazioni, ottenuto dall'app dal sistema o implementato dall'utente.
  • Finestra padre, rappresentata dal tipo CoreWindow.
  • Origine per tutti gli eventi di attivazione. Sia le visualizzazioni sia le finestre hanno eventi di attivazione separati.

In sintesi, l'oggetto app fornisce una factory del provider di visualizzazioni. Crea un provider di visualizzazioni e crea un'istanza di una finestra padre per l'app. Il provider di visualizzazioni definisce la visualizzazione dell'app per la finestra padre dell'app stessa. Prendiamo ora in esame le specifiche della visualizzazione e della finestra padre.

Comportamenti e proprietà CoreApplicationView

CoreApplicationView rappresenta la visualizzazione dell'app corrente. Il singleton dell'app crea la visualizzazione dell'app durante l'inizializzazione, ma la visualizzazione rimane inattiva finché non viene attivata. È possibile ottenereCoreWindow che visualizza la vista accedendo alla proprietà CoreApplicationView.CoreWindow ed è possibile gestire gli eventi di attivazione e disattivazione per la visualizzazione registrando delegati con l'evento CoreApplicationView.Activated.

Comportamenti e proprietà di CoreWindow

La finestra padre, che è un'istanza CoreWindow, viene creata e passata al provider di visualizzazione quando l'oggetto app viene inizializzato. Se l'app ha una finestra da visualizzare, la visualizza; in caso contrario, inizializza semplicemente la visualizzazione.

CoreWindow offre diversi eventi specifici per i comportamenti di input e finestra di base. È possibile gestire questi eventi registrando i propri delegati.

È anche possibile ottenere il dispatcher eventi della finestra per la finestra accedendo alla proprietà CoreWindow.Dispatcher, che fornisce un'istanza di CoreDispatcher.

Comportamenti e proprietà di CoreDispatcher

È possibile determinare il comportamento di threading dell'invio di eventi per una finestra con il tipo CoreDispatcher. In questo tipo esiste un metodo particolarmente importante: il metodo CoreDispatcher.ProcessEvents, che avvia l'elaborazione degli eventi della finestra. La chiamata di questo metodo con l'opzione errata per l'app può portare a tutti i tipi di comportamenti di elaborazione imprevisti degli eventi.

Opzione CoreProcessEventsOption Descrizione
CoreProcessEventsOption.ProcessOneAndAllPending Inviare tutti gli eventi attualmente disponibili nella coda. Se non sono presenti eventi in sospeso, attendi il nuovo evento successivo.
CoreProcessEventsOption.ProcessOneIfPresent Inviare un evento se è in sospeso nella coda. Se non sono presenti eventi in sospeso, non attendere che venga generato un nuovo evento, ma restituire immediatamente.
CoreProcessEventsOption.ProcessUntilQuit Attendere nuovi eventi e inviare tutti gli eventi disponibili. Continua con questo comportamento finché la finestra non viene chiusa o l'applicazione chiama il metodo Close sull'istanza CoreWindow.
CoreProcessEventsOption.ProcessAllIfPresent Inviare tutti gli eventi attualmente disponibili nella coda. Se non sono presenti eventi in sospeso, restituire immediatamente.

UWP che utilizza DirectX deve usare l'opzione CoreProcessEventsOption.ProcessAllIfPresent per impedire comportamenti di blocco che potrebbero interrompere gli aggiornamenti grafici.

Considerazioni su ASTA per sviluppatori DirectX

L'oggetto app che definisce la rappresentazione in fase di esecuzione della tua app UWP e DirectX usa un modello di threading denominato Application Single-Threaded Apartment (ASTA) per ospitare le visualizzazioni dell'interfaccia utente dell'app. Se stai sviluppando un'app UWP e DirectX, hai familiarità con le proprietà di una ASTA, perché ogni thread inviato dalla tua app UWP e DirectX deve usare le API Windows::System::Threading o utilizzare CoreWindow::CoreDispatcher. (È possibile ottenere l'oggetto CoreWindow per ASTA chiamando CoreWindow::GetForCurrentThread dalla tua app.)

L'aspetto più importante da tenere presente, in qualità di sviluppatore di un'app DirectX UWP, è che devi abilitare il thread dell'app per inviare thread MTA impostando Platform::MTAThread su main().

[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory 
    CoreApplication::Run(myDXAppSource);
    return 0;
}

Quando l'oggetto app per l'app DirectX UWP viene attivato, crea l'ASTA che verrà usata per la visualizzazione dell'interfaccia utente. Il nuovo thread ASTA chiama nella factory del provider di visualizzazioni, per creare il provider di visualizzazioni per l'oggetto app e, di conseguenza, il codice del provider di visualizzazioni verrà eseguito su tale thread ASTA.

Inoltre, qualsiasi thread che derivi da ASTA deve essere in un MTA. Tieni presente che tutti i thread MTA che si avviano possono comunque creare problemi di reentrancy e causare un deadlock.

Se stai eseguendo la conversione di un codice esistente per l'esecuzione sul thread ASTA, tieni presenti queste considerazioni:

  • Le primitive di attesa, come CoWaitForMultipleObjects, si comportano in modo diverso in una ASTA rispetto a una STA.

  • Il ciclo modale di chiamata COM opera in modo diverso in una ASTA. Non è più possibile ricevere chiamate non correlate mentre è in corso una chiamata in uscita. Ad esempio, il comportamento seguente creerà un deadlock da una ASTA (e arresta immediatamente l'app):

    1. L'ASTA chiama un oggetto MTA e passa un puntatore di interfaccia P1.
    2. Successivamente, l'ASTA chiama lo stesso oggetto MTA. L'oggetto MTA chiama P1 prima di tornare all'ASTA.
    3. P1 non può entrare nell'ASTA perché è bloccato effettuando una chiamata non correlata. Tuttavia, il thread MTA viene bloccato mentre tenta di effettuare la chiamata a P1.

    Per risolvere il problema:

    • Utilizzando il modello asincrono definito nella Parallel Patterns Library (PPLTasks.h)
    • Chiamando CoreDispatcher::ProcessEvents dall'ASTA dell'app (il thread principale dell'app) appena possibile per consentire chiamate arbitrarie.

    Detto questo, non è possibile fare affidamento sul recapito immediato di chiamate non correlate all'ASTA della tua app. Per altre informazioni sulle chiamate asincrone, consulta Programmazione asincrona in C++.

In generale, durante la progettazione dell'app UWP, usa CoreDispatcher per CoreWindow e CoreDispatcher::ProcessEvents della tua app per gestire tutti i thread dell'interfaccia utente anziché provare a creare e gestire manualmente i thread MTA. Quando è necessario un thread separato che non è possibile gestire con CoreDispatcher, usa i modelli asincroni e segui le indicazioni fornite in precedenza per evitare problemi di reentrancy.