應用程式物件和 DirectX

具有 DirectX 的通用 Windows 平台 (UWP) 遊戲不會使用許多 Windows UI 使用者介面元素和物件。 相反,因為它們在 Windows 執行階段堆疊中的較低層級執行,所以它們必須以更基本的方式與使用者介面架構進行互通:直接存取應用程式物件並與之互通。 了解這種互通何時以及如何發生,以及作為 DirectX 開發人員如何在 UWP 應用程式的開發中有效地使用此模型。

有關閱讀時遇到不熟悉圖形術語或概念的資訊,請參閱 Direct3D 圖形詞彙表

重要的核心使用者介面命名空間

首先,讓我們記下必須在 UWP 應用程式中包含 (使用) 的 Windows 執行階段命名空間。 稍後我們會進一步介紹。

注意

如果不開發 UWP 應用,請使用特定 JavaScript 或 XAML 程式庫和命名空間中提供的使用者介面元件,而不是這些命名空間中提供的類型。

Windows 執行階段應用程式物件

在 UWP 應用程式中,您要取得視窗和一個檢視提供者,您可以從中取得檢視並可以將交換鏈 (您的顯示緩衝區) 連接到其中。 您也可以將此檢視掛鉤到執行中應用程式的特定視窗事件中。 若要取得由 CoreWindow 類型定義的應用程式物件的父視窗,請建立實作 IFrameworkViewSource 的類型。 有關示範如何實作 IFrameworkViewSourceC++/WinRT 程式碼範例,請參閱使用 DirectX 和 Direct2D 撰寫原生互通

以下是使用核心使用者介面架構取得視窗的基本步驟集。

  1. 建立實作 IFrameworkView 的類型。 這是您的檢視。

    在此類型中,定義:

    • Initialize 方法,將 CoreApplicationView 執行個體作為參數。 您可以呼叫 CoreApplication.CreateNewView 來取得此類型的執行個體。 應用程式物件會在應用程式啟動時呼叫它。
    • SetWindow 方法,將 CoreWindow 執行個體作為參數。 您可以透過存取新 CoreApplicationView 執行個體上的 CoreWindow 屬性來取得此類型的執行個體。
    • Load 方法,將進入點字串作為唯一參數。 當您呼叫此方法時,應用程式物件會提供進入點字串。 這是您設定資源的位置。 您可以在這裡建立裝置資源。 應用程式物件會在應用程式啟動時呼叫它。
    • Run 方法,可啟動 CoreWindow 物件並啟動視窗事件發送器。 當應用程式的處理序啟動時,應用程式物件會呼叫它。
    • Uninitialize 方法,可清除呼叫 Load 時所設定的資源。 當應用程式關閉時,應用程式物件會呼叫此方法。
  2. 建立實作 IFrameworkViewSource 的類型。 這是您的檢視提供者。

    在此類型中,定義:

  3. 將檢視提供者的執行個體從 main 傳遞到 CoreApplication.Run

記住這些基礎知識後,讓我們看看擴充此方法所需的其他選項。

核心使用者介面類型

以下是 Windows 執行階段中可能對您有用的其他核心使用者介面類型:

您可以使用這些類型來存取應用程式的檢視,特別是繪製應用程式父視窗內容的位元,並處理針對該視窗觸發的事件。 應用程式視窗的處理序是一個獨立的應用程式單一執行緒 Apartment (ASTA),其會處理所有回呼。

您的應用程式檢視是由應用程式視窗的檢視提供者所產生,而且大部分情況下會由特定架構套件或系統本身實作,因此您不需要自行實作。 對於 DirectX,您需要實作一個精簡檢視提供者,如前所述。 以下組件和行為之間存在特定的一對一關係:

  • 應用程式的檢視由 CoreApplicationView 類型表示,並定義更新視窗的方法。
  • ASTA,其屬性定義了應用程式的執行緒行為。 您無法在 ASTA 上建立 COM STA 屬性類型的執行個體。
  • 檢視提供者,您的應用程式從系統取得或由您實作。
  • 父視窗,由 CoreWindow 類型表示。
  • 所有啟用事件的來源。 檢視和視窗都有單獨的啟用事件。

總之,應用程式物件會提供檢視提供者處理站。 它會建立檢視提供者,並具現化應用程式的父視窗。 檢視提供者會定義應用程式父視窗的應用程式檢視。 現在,讓我們討論檢視和父視窗的細節。

CoreApplicationView 行為和屬性

CoreApplicationView 代表目前的應用程式檢視。 應用程式單例模式會在初始化期間建立應用程式檢視,但檢視會保持休眠狀態,直到啟動為止。 您可以透過存取 CoreApplicationView.CoreWindow 屬性來取得顯示檢視的 CoreWindow,並且可以透過向 CoreApplicationView.Activated 事件註冊委派來處理檢視的啟動和停用事件。

CoreWindow 行為和屬性

父視窗是 CoreWindow 執行個體,會在應用程式物件初始化時建立並傳遞至檢視提供者。 如果應用程式有一個視窗要顯示,則會顯示它; 否則,它只會初始化檢視。

CoreWindow 提供了許多特定輸入和基本視窗行為的事件。 您可以透過向這些事件註冊您自己的委派來處理這些事件。

您也可以透過存取 CoreWindow.Dispatcher 屬性來取得視窗的視窗事件發送器,該屬性提供了 CoreDispatcher 的執行個體。

CoreDispatcher 行為和屬性

您可以使用 CoreDispatcher 類型來決定視窗的事件分派的執行緒行為。 對於這種類型,有一個特別重要的方法:CoreDispatcher.ProcessEvents 方法,該方法會啟動視窗事件處理。 使用錯誤的應用程式選項呼叫此方法可能會導致各種意外的事件處理行為。

CoreProcessEventsOption 選項 描述
CoreProcessEventsOption.ProcessOneAndAllPending 分派佇列中所有目前可用的事件。 如果沒有待處理的事件,則等待下一個新事件。
CoreProcessEventsOption.ProcessOneIfPresent 如果事件在佇列中待處理,則分派一個事件。 如果沒有待處理的事件,則不要等待引發新事件,而是立即返回。
CoreProcessEventsOption.ProcessUntilQuit 等待新事件並分派所有可用事件。 繼續此行為,直到視窗關閉或應用程式呼叫 CoreWindow 執行個體上的 Close 方法。
CoreProcessEventsOption.ProcessAllIfPresent 分派佇列中所有目前可用的事件。 如果沒有待處理的事件,則立即返回。

使用 DirectX 的 UWP 應使用 CoreProcessEventsOption.ProcessAllIfPresent 選項來防止可能中斷圖形更新的封鎖行為。

DirectX 開發人員的 ASTA 注意事項

定義 UWP 和 DirectX 應用程式執行階段表示的應用程式物件,會使用名為應用程式單一執行緒 Apartment (ASTA) 的執行緒模型來託管應用程式的 UI 檢視。 如果您正在開發 UWP 和 DirectX 應用程式,那您會很熟悉 ASTA 的屬性,因為從 UWP 和 DirectX 應用程式分派的任何線程都必須使用 Windows::System::Threading API,或使用 CoreWindow::CoreDispatcher。 (您可以透過從應用程式呼叫 CoreWindow::GetForCurrentThread 來取得 ASTA 的 CoreWindow 物件。)

作為 UWP DirectX 應用程式的開發人員,您最需要注意的事情是,您必須透過在 main() 上設定 Platform::MTAThread,來使應用程式執行緒能夠分派 MTA 執行緒。

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

當 UWP DirectX 應用程式的應用程式物件啟動時,它會建立將用於 UI 檢視的 ASTA。 新的 ASTA 執行緒呼叫您的檢視提供者處理站,為您的應用程式物件建立檢視提供者,因此您的檢視提供者程式碼將會在該 ASTA 執行緒上執行。

此外,從 ASTA 分拆的所有線程都必須位於 MTA 中。 請注意,您分拆的任何 MTA 執行緒仍然會產生重入問題並導致鎖死。

如果您要移植現有程式碼以在 ASTA 執行緒上執行,請記住以下注意事項:

  • 等待原始物件 (例如 CoWaitForMultipleObjects) 在 ASTA 中的行為與在 STA 中的行為不同。

  • COM 呼叫強制回應迴圈在 ASTA 中的運作方式不同。 在撥出電話時,您將無法再接聽不相關的電話。 例如,以下行為將從 ASTA 中建立鎖死 (並立即使應用程式崩潰):

    1. ASTA 會呼叫 MTA 物件,並傳遞介面指標 P1。
    2. 隨後,ASTA 會呼叫同一個 MTA 物件。 MTA 物件會在返回 ASTA 之前呼叫 P1。
    3. P1 無法進入 ASTA,因為它無法進行不相關的呼叫。 但是,MTA 執行緒會在嘗試呼叫 P1 時遭到封鎖。

    您可以透過以下方式解決此問題:

    • 使用平行模式庫 (PPLTasks.h) 中定義的非同步模式
    • 盡快從應用程式的 ASTA (應用程式的主執行緒) 呼叫 CoreDispatcher::ProcessEvents 以允許任意呼叫。

    也就是說,您不能依賴立即向應用程式的 ASTA 傳送不相關的呼叫。 有關非同步呼叫的更多資訊,請參閱 C++ 中的非同步程式設計

總體而言,在設計 UWP 應用程式時,請使用應用程式的 CoreWindowCoreDispatcherCoreDispatcher::ProcessEvents 來處理所有 UI 線程,而不是嘗試自行建立和管理 MTA 線程。 當您需要一個無法使用 CoreDispatcher 處理的單獨線程時,請使用非同步模式,並遵循先前所述的指南以避免重入問題。