建立和裝載應用程式延伸模組

本文向你展示如何建立 Windows 10 應用程式延伸模組並將其託管在應用程式中。 UWP 應用程式和套件桌面應用程式支援應用程式延伸模組。

為了示範如何建立應用延伸模組,本文使用包清單 XML 和數學延伸模組程式碼範例中的程式碼片段。 此範例是 UWP 應用程式,但範例中示範的功能也適用於封包桌面應用程式。 請按照以下說明開始使用範例:

  • 下載並解壓縮數學延伸模組程式代碼範例
  • 在 Visual Studio 2019 中,開啟 MathExtensionSample.sln。 將建置類型設定為 x86 (Build> Configuration Manager,然後將兩個專案的平台變更為 x86)。
  • 部署方案:建置>部署解決方案

應用程式延伸模組簡介

在 Windows 10 中,應用程式延伸模組提供類似於外掛程式、增益集和附加元件在其他平台上所執行的功能。 應用程式擴充功能是在 Windows 10 年度更新版 (版本 1607 組建 10.0.14393) 中引進的。

應用程式延伸模組是 UWP 應用程式或封裝桌面應用程式,它們具有擴充聲明,允許它們與主機應用程式共用內容和部署事件。 延伸模組應用程式可以提供多個延伸模組。

由於應用程式擴充只是 UWP 應用程式或封裝桌面應用程式,因此它們也可以是功能齊全的應用程式、託管延伸模組並為其他應用程式提供延伸模組 - 所有這些都無需創建單獨的應用程式封包。

當您建立應用程式延伸模組主機時,您就創造了一個圍繞您的應用程式開發生態系統的機會,在該生態系統中,其他開發人員可以以您可能沒有預料到或沒有資源的方式增強您的應用程式。 請考慮 Microsoft Office 延伸模組、Visual Studio 延伸模組、瀏覽器延伸模組等。這些為應用程式創造了超越其附帶功能的更豐富的體驗。 延伸模組可以為您的應用程式增加價值並延長其使用壽命。

概括地說,要建立應用延伸模組關係,我們需要:

  1. 將應用程式聲明為延伸模組主機。
  2. 將應用程式聲明為延伸模組。
  3. 決定是否將擴充實作為應用程式服務、背景工作或其他方式。
  4. 定義主機及其分機的通訊方式。
  5. 使用主應用程式中的 Windows.ApplicationModel.AppExtensions API 來存取延伸模組。

讓我們看看如何完成這項作業,方法是檢查 數學延伸模組程式代碼範例,此範例會實作假設的計算機,您可以使用延伸模組將新的函式加入其中。 在 Microsoft Visual Studio 2019 中,從程式碼範例載入 MathExtensionSample.sln

Math Extension code sample

將應用程式聲明為延伸模組主機

應用程式透過在其 Package.appxmanifest 檔案中聲明 <AppExtensionHost> 元素來將自身標識為應用程式延伸模組主機。 請參閱 MathExtensionHost 專案中的 Package.appxmanifest 檔案以了解這是如何完成的。

MathExtensionHost 專案 中的 Package.appxmanifest

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
            <uap3:Extension Category="windows.appExtensionHost">
                <uap3:AppExtensionHost>
                  <uap3:Name>com.microsoft.mathext</uap3:Name>
                </uap3:AppExtensionHost>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

注意 xmlns:uap3="http://..."IgnorableNamespaces 中的 uap3 存在。 這些都是必要的,因為我們使用 uap3 命名空間。

<uap3:Extension Category="windows.appExtensionHost"> 將此應用程式識別為擴充主機。

<uap3:AppExtensionHost> 中的 Name 元素是擴充合約名稱。 當延伸模組指定相同的延伸模組合約名稱時,主機將能夠找到它。 依照慣例,建議您使用您的應用程式或發行者名稱來建置延伸模塊合約名稱,以避免與其他延伸模塊合約名稱可能發生衝突。

您可以在同一個應用程式中定義多個主機和多個延伸模組。 在此範例中,我們會宣告一個主機。 延伸模組定義於另一個應用程式中。

將應用程式聲明為延伸模組

應用程式透過在其 Package.appxmanifest 檔案中聲明 <uap3:AppExtension> 元素來將自身標識為應用程式延伸模組主機。 開啟 MathExtension 專案中的 Package.appxmanifest 檔案以了解這是如何完成的。

MathExtension 專案中的 Package.appxmanifest

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
          ...
          <uap3:Extension Category="windows.appExtension">
            <uap3:AppExtension Name="com.microsoft.mathext"
                               Id="power"
                               DisplayName="x^y"
                               Description="Exponent"
                               PublicFolder="Public">
              <uap3:Properties>
                <Service>com.microsoft.powservice</Service>
              </uap3:Properties>
              </uap3:AppExtension>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

再次注意 xmlns:uap3="http://..." 行以及 IgnorableNamespacesuap3 的存在。 這些都是必要的,因為我們使用 uap3 命名空間。

<uap3:Extension Category="windows.appExtension"> 將此+識別為擴充。

<uap3:AppExtension> 屬性意義如下:

屬性 描述 必要
名稱 這是延伸模組合約名稱。 當它符合主機中宣告的 Name 時,該主機將能夠找到此延伸模組。 ✔️
識別碼 可唯一識別此延伸模組。 由於可以使用相同延伸模組合約名稱的多個延伸模組(想像一下支援數個延伸模組的油漆應用程式),因此您可以使用標識碼來區分它們。 應用程式延伸模組主機可以使用標識碼來推斷延伸模組類型的相關內容。 例如,您可以有一個延伸模組專為桌面設計,另一個適用於行動裝置,且標識符為區分器。 您也可以使用 下面討論的 Properties 元素。 ✔️
DisplayName 您可以從主機應用程式使用,以識別使用者的延伸模組。 其可從 中查詢,而且可以使用 新的資源管理系統 (ms-resource:TokenName) 進行當地語系化。 當地語系化的內容會從應用程式延伸模組套件載入,而不是主機應用程式。
說明 可以從您的主機應用程式中使用來向使用者描述延伸模組。 其可從 中查詢,而且可以使用 新的資源管理系統 (ms-resource:TokenName) 進行當地語系化。 當地語系化的內容會從應用程式延伸模組套件載入,而不是主機應用程式。
公用資料夾 資料夾的名稱,相對於套件根目錄,您可以在其中與延伸模組主機共用內容。 依照慣例,名稱為「Public」,但您可以使用任何符合延伸模組中資料夾的名稱。 ✔️

<uap3:Properties> 是一個可選元素,包含主機可以在執行時讀取的自訂中繼資料。 在程式代碼範例中,延伸模組會實作為應用程式服務,因此主機需要一種方式來取得該應用程式服務的名稱,以便呼叫它。 應用程式服務的名稱定義於<服務>元素中,我們定義了它 (我們可以將其命名為我們想要的任何專案)。 程式代碼範例中的主機會在執行時間尋找此屬性,以了解應用程式服務的名稱。

決定如何實作延伸模組。

關於應用程式延伸模組的 Build 2016 會議示範如何使用主機和延伸模組之間共用的公用資料夾。 在該範例中,延伸模組由儲存在主機呼叫的公共資料夾中的 JavaScript 檔案實作。 這種方法的優點是輕量型、不需要編譯,而且可以支援建立預設登陸頁面,以提供延伸模組和主機應用程式 Microsoft Store 頁面連結的指示。 如需詳細資訊,請參閱組建 2016 應用程式延伸模組程式代碼範例。 具體來說,請參閱 InvertImageExtension 專案和 ExtensibilitySample 專案中 ExtensionManager.cs 的 InvokeLoad()

在此範例中,我們將使用應用程式服務來實作延伸模組。 應用程式服務具有下列優點:

  • 如果擴充程式崩潰,它不會關閉主機應用程式,因為主機應用程式在自己的程序中執行。
  • 您可以使用您選擇的語言來實作服務。 它不需要比對用來實作主機應用程式的語言。
  • 應用程式服務可以存取其自己的應用程式容器,該容器可能具有與主機不同的功能。
  • 服務中的數據與主應用程式之間有隔離。

主機應用程式服務代碼

以下是呼叫延伸模組的應用程式服務的主機程式碼:

MathExtensionHost 專案中的 ExtensionManager.cs

public async Task<double> Invoke(ValueSet message)
{
    if (Loaded)
    {
        try
        {
            // make the app service call
            using (var connection = new AppServiceConnection())
            {
                // service name is defined in appxmanifest properties
                connection.AppServiceName = _serviceName;
                // package Family Name is provided by the extension
                connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

                // open the app service connection
                AppServiceConnectionStatus status = await connection.OpenAsync();
                if (status != AppServiceConnectionStatus.Success)
                {
                    Debug.WriteLine("Failed App Service Connection");
                }
                else
                {
                    // Call the app service
                    AppServiceResponse response = await connection.SendMessageAsync(message);
                    if (response.Status == AppServiceResponseStatus.Success)
                    {
                        ValueSet answer = response.Message as ValueSet;
                        if (answer.ContainsKey("Result")) // When our app service returns "Result", it means it succeeded
                        {
                            return (double)answer["Result"];
                        }
                    }
                }
            }
        }
        catch (Exception)
        {
             Debug.WriteLine("Calling the App Service failed");
        }
    }
    return double.NaN; // indicates an error from the app service
}

這是呼叫應用程式服務的典型程式碼。 如需如何實作和呼叫 App Service 的詳細資訊,請參閱如何建立和使用 App Service

要注意的一件事是決定要呼叫之 App Service 的名稱。 由於主機沒有有關延伸模組程序實現的資訊,因此延伸模組程序需要提供其應用服務的名稱。 在程式碼範例中,延伸模組在 <uap3:Properties> 元素的檔案中聲明應用服務的名稱:

MathExtension 專案中的 Package.appxmanifest

    ...
    <uap3:Extension Category="windows.appExtension">
      <uap3:AppExtension ...>
        <uap3:Properties>
          <Service>com.microsoft.powservice</Service>
        </uap3:Properties>
        </uap3:AppExtension>
    </uap3:Extension>

您可以在 <uap3:Properties> 元素中定義自己的 XML。 在此情況下,我們會定義應用程式服務的名稱,讓主機在叫用延伸模組時可以它。

當主機載入延伸模組時,以下程式碼從延伸模組的 Package.appxmanifest 中定義的屬性中提取服務名稱:

Update()MathExtensionHost 專案中的 ExtensionManager.cs

...
var properties = await ext.GetExtensionPropertiesAsync() as PropertySet;

...
#region Update Properties
// update app service information
_serviceName = null;
if (_properties != null)
{
   if (_properties.ContainsKey("Service"))
   {
       PropertySet serviceProperty = _properties["Service"] as PropertySet;
       this._serviceName = serviceProperty["#text"].ToString();
   }
}
#endregion

透過儲存在 _serviceName 中的應用程式服務的名稱,主機可以使用它來呼叫應用程式服務。

呼叫 App Service 也需要包含 App Service 之套件的系列名稱。 幸運的是,應用程式延伸模組API 會提供這一行中取得的資訊:connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

定義主機和延伸模組的通訊方式

應用程式服務會使用 ValueSet 來交換資訊。 身為主機的作者,您必須想出通訊協定,以與彈性的延伸模組通訊。 在程式代碼範例中,這表示考慮未來可能需要 1、2 或更多自變數的延伸模組。

在這裡範例中,自變數的通訊協定是 ValueSet,其中包含名為 'Arg' + 自變數編號的索引鍵值組,例如 Arg1Arg2。 主機會傳遞 ValueSet 中的所有自變數,而延伸模組會利用它所需的自變數。 如果延伸模組能夠計算結果,則主機預期 從延伸模組傳回的 ValueSet 具有包含 Result 計算值的索引鍵。 如果該金鑰不存在,主機將假定擴充無法完成計算。

延伸模組應用服務代碼

在程式碼範例中,延伸模組的應用程式服務未作為背景工作實作。 相反,它使用單程序應用程式服務模型,其中應用程式服務與託管它的擴充應用程式在同一程序中執行。 這仍然是與主機應用程式不同的程序,提供了程序分離的好處,同時透過避免延伸模組程序和實現應用程式服務的背景程序之間的跨程序通訊來獲得一些效能優勢。 請參閱將應用程式服務轉換為與其主機應用程式在相同程序中執行,以了解作為背景工作執行的應用程式服務與在相同程序中執行的應用程式服務之間的差異。

當應用程式服務被啟動時,系統會發出 OnBackgroundActivate()。 此程式碼設定事件處理程序來處理實際的應用程式服務呼叫 (OnAppServiceRequestReceived()) 以及處理內務事件,例如取得處理取消或關閉事件的延遲物件。

MathExtension 專案中的 App.xaml.cs。

protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    base.OnBackgroundActivated(args);

    if ( _appServiceInitialized == false ) // Only need to setup the handlers once
    {
        _appServiceInitialized = true;

        IBackgroundTaskInstance taskInstance = args.TaskInstance;
        taskInstance.Canceled += OnAppServicesCanceled;

        AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
        _appServiceDeferral = taskInstance.GetDeferral();
        _appServiceConnection = appService.AppServiceConnection;
        _appServiceConnection.RequestReceived += OnAppServiceRequestReceived;
        _appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
    }
}

執行延伸模組工作的程式代碼位於 OnAppServiceRequestReceived() 中。 叫用應用程式服務來執行計算時,會呼叫此函式。 它從 ValueSet 中提取所需的值。 如果它可以進行計算,則會將結果放在傳回給主機的 ValueSet 中名為 Result 的鍵下。 回想一下,根據為該主機及其延伸模組的通訊方式定義的協議,Result 金鑰的存在將指示成功;否則失敗。

MathExtension 專案中的 App.xaml.cs。

private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    // Get a deferral because we use an awaitable API below (SendResponseAsync()) to respond to the message
    // and we don't want this call to get cancelled while we are waiting.
    AppServiceDeferral messageDeferral = args.GetDeferral();
    ValueSet message = args.Request.Message;
    ValueSet returnMessage = new ValueSet();

    double? arg1 = Convert.ToDouble(message["arg1"]);
    double? arg2 = Convert.ToDouble(message["arg2"]);
    if (arg1.HasValue && arg2.HasValue)
    {
        returnMessage.Add("Result", Math.Pow(arg1.Value, arg2.Value)); // For this sample, the presence of a "Result" key will mean the call succeeded
    }

    await args.Request.SendResponseAsync(returnMessage);
    messageDeferral.Complete();
}

管理延伸模組

現在我們已經了解如何實現主機與其延伸模組之間的關係,讓我們看看主機如何查找系統上安裝的延伸模組,並對包含延伸模組的包的添加和刪除做出反應。

Microsoft Store 以套件的形式提供擴充。 AppExtensionCatalog 會尋找已安裝的套件,其中包含符合主機延伸模組合約名稱的延伸模組,並提供安裝或移除與主機相關的應用程式延伸模組套件時引發的事件。

在程式碼範例中,ExtensionManager 類別 (在 MathExtensionHost 專案的 ExtensionManager.cs 中定義) 包裝了載入延伸模組以及回應擴充包安裝和卸載的邏輯。

ExtensionManager 建構函式使用 AppExtensionCatalog 來尋找系統上與主機具有相同擴充合約名稱的應用程式擴充:

MathExtensionHost 專案中的 ExtensionManager.cs。

public ExtensionManager(string extensionContractName)
{
   // catalog & contract
   ExtensionContractName = extensionContractName;
   _catalog = AppExtensionCatalog.Open(ExtensionContractName);
   ...
}

安裝擴充套件時,ExtensionManager 會收集有關套件中與主機具有相同擴充合約名稱的延伸模組的資訊。 安裝可能代表更新,在這種情況下,受影響的延伸模組的資訊會更新。 卸載延伸模組套件時,會 ExtensionManager 移除受影響延伸模組的相關信息,讓使用者知道哪些延伸模組已無法使用。

Extension 類別 (在 MathExtensionHost 專案的 ExtensionManager.cs 中定義) 是為程式碼範例建立的,用於存取延伸模組的 ID、描述、標誌和應用程式特定資訊 (例如使用者是否已啟用該延伸模組)。

假設延伸模組已載入 (請參閱 Load()ExtensionManager.cs 中),表示套件狀態良好,而且我們已取得其標識碼、標誌、描述和公用資料夾 (我們在此範例中不會使用它,而只是為了示範如何取得它)。 延伸模組套件本身並未載入。

卸除的概念用於追蹤應該不再向用戶呈現哪些延伸模組。

ExtensionManager 提供了一個集合 Extension 執行個體,以便延伸模組、它們的名稱、描述和標誌可以資料綁定到 UI。 ExtensionsTab 頁面會系結至此集合,並提供 UI 來啟用/停用延伸模組,以及移除它們。

Extensions tab example UI

拿掉延伸模組時,系統會提示使用者確認要卸載包含延伸模組的套件 (可能包含其他延伸模組)。 如果使用者同意,則會卸載套件,並從 ExtensionManager 主機應用程式可用的延伸模組清單中移除卸載套件中的延伸模組。

Uninstall UI

偵錯應用程式延伸模組和主機

擴充主機和延伸模組通常不是相同解決方案的一部分。 在此情況下,若要偵錯主機和延伸模組:

  1. 在 Visual Studio 的一個執行個體中載入主專案。
  2. 在另一個 Visual Studio 執行個體中載入延伸模組。
  3. 在偵錯程式中啟動主機應用程式。
  4. 在偵錯程式中啟動延伸模組應用程式。 (如果您想要部署延伸模組,而不是偵錯它,以測試主機的套件安裝事件,請改為執行建置>部署解決方案)。

現在,您將能夠在主機和延伸模組中叫用斷點。 如果您開始偵錯延伸模組應用程式本身,您會看到應用程式的空白視窗。 如果您不想看到空白窗口,可以更改延伸模組項目的偵錯設置,以不啟動應用程式,而是在啟動時對其進行偵錯 (右鍵點擊延伸模組項目,Properties>Debug> 選擇不啟動,但偵錯我的代碼啟動時) 您仍然需要開始偵錯 F5 延伸模組項目,但它將等到主機啟動延伸模組,然後將命中延伸模組中的斷點。

偵錯程式代碼範例

在程式代碼範例中,主機和延伸模組位於相同的解決方案中。 執行以下操作進行偵錯:

  1. 確保 MathExtensionHost 是啟動專案 (右鍵點選 MathExtensionHost 專案,點選設定為啟動專案)。
  2. MathExtensionHost 專案的 ExtensionManager.cs 中 Invoke 放置一個斷點。
  3. F5 執行 MathExtensionHost 專案。
  4. MathExtension 專案的 App.xaml.cs 中 OnAppServiceRequestReceived 放置一個斷點。
  5. 開始偵錯 MathExtension 專案 (右鍵點擊 MathExtension 專案,偵錯>啟動新執行個體),這將部署它並觸發主機中的套件安裝事件。
  6. MathExtensionHost 應用程式中,瀏覽至計算頁面,然後按一下 x^y 以啟動延伸模組。 Invoke() 斷點會先叫用,您可以看到正在呼叫延伸模組 App Service。 然後點選擴充中的 OnAppServiceRequestReceived() 方法,就可以看到應用程式服務計算出結果並回傳。

針對實作為 App Service 的延伸模組進行疑難解答

如果您的擴充主機在連接到延伸模組的應用程式服務時遇到問題,請確保 <uap:AppService Name="..."> 屬性與您在 <Service> 元素中放置的內容相符。 如果它們不相符,延伸模組所提供的服務名稱不符合您實作的App Service名稱,且主機將無法啟用您的延伸模組。

MathExtension 專案中的 Package.appxmanifest

<Extensions>
   <uap:Extension Category="windows.appService">
     <uap:AppService Name="com.microsoft.sqrtservice" />      <!-- This must match the contents of <Service>...</Service> -->
   </uap:Extension>
   <uap3:Extension Category="windows.appExtension">
     <uap3:AppExtension Name="com.microsoft.mathext" Id="sqrt" DisplayName="Sqrt(x)" Description="Square root" PublicFolder="Public">
       <uap3:Properties>
         <Service>com.microsoft.powservice</Service>   <!-- this must match <uap:AppService Name=...> -->
       </uap3:Properties>
     </uap3:AppExtension>
   </uap3:Extension>
</Extensions>   

要測試的基本案例檢查清單

當您建置擴充主機並準備好測試它支援延伸模組的方式時,以下是一些要嘗試的基本案例:

  • 執行主機,然後部署延伸模組應用程式
    • 主機在執行時會挑選隨附的新延伸模組嗎?
  • 部署延伸模組應用程式,然後部署並執行主機。
    • 主機會挑選先前存在的延伸模組嗎?
  • 執行主機,然後移除延伸模組應用程式。
    • 主機是否正確偵測移除?
  • 執行主機,然後將延伸模組應用程式更新為較新版本。
    • 主機會正確挑選變更並卸除舊版延伸模組嗎?

要測試的進階案例:

  • 執行主機,將延伸模組應用程式移至可移動媒體,刪除媒體
    • 主機是否偵測封裝狀態中的變更,並停用延伸模組?
  • 執行主機,然後損毀延伸模組應用程式 (使其無效、以不同方式簽署等等)
    • 主機是否偵測到遭竄改的延伸模組並正確處理?
  • 執行主機,然後部署內容或屬性無效的擴充應用程式
    • 主機是否偵測到無效的內容並正確處理?

設計考量

  • 提供 UI,向使用者顯示哪些延伸模組可用,並允許他們啟用/停用它們。 您也可以考慮為由於封包離線等原因而無法使用的延伸模組新增標誌符號。
  • 將用戶導向到可取得延伸模組的位置。 您的延伸模組頁面或許可以提供 Microsoft Store 搜尋查詢,以列出可與您的應用程式搭配使用的延伸模組清單。
  • 請考慮如何通知使用者新增和移除延伸模組。 您可以在安裝新的延伸模組時建立通知,並邀請用戶啟用它。 延伸模組預設應停用,讓使用者能夠控制。

應用程式延伸模組與選擇性套件有何不同

可選套件和應用程式延伸模組之間的主要區別是開放生態系統與封閉生態系統,以及依賴套件與獨立套件。

應用程式延伸模組會參與開放式生態系統。 如果您的應用程式可以裝載應用程式延伸模組,只要他們符合您從延伸模塊傳遞/接收資訊的方法,任何人都可以為您的主機撰寫延伸模組。 這與參與封閉式生態系統的選用套件不同,發行者決定允許誰建立可與應用程式搭配使用的選擇性套件。

應用程式延伸模組是獨立的套件,而且可以是獨立應用程式。 它們不能具有對其他應用的部署依賴關係。 選用套件必須有主要套件,沒有就無法執行。

遊戲的擴充套件將是可選套件的良好候選者,因為它與遊戲緊密綁定,它不能獨立於遊戲執行,並且您可能不希望延伸模組套件由生態系統中的任何開發人員建立。

如果同一款遊戲具有可自訂的 UI 附加元件或主題,那麼應用程式延伸模組可能是一個不錯的選擇,因為提供延伸模組的應用程式可以自行執行,並且任何第 3 方都可以製作它們。

備註

本主題介紹應用程式延伸模組。 需要注意的關鍵事項是建立主機並在其 Package.appxmanifest 檔案中對其進行標記,建立延伸模組並在其 Package.appxmanifest 檔案中對其進行標記,確定如何實現延伸模組 (例如應用程式服務)、背景工作或其他方式,定義主機如何與延伸模組通訊,以及使用 AppExtensions API 存取和管理延伸模組。