共用方式為


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

本文說明如何建立 Windows 10 應用程式延伸模組,並將其裝載在應用程式中。 UWP 應用程式和 已封裝的桌面應用程式都支援應用程式擴充功能。

為了示範如何建立應用程式延伸模組,本文將使用來自 Math Extension 代碼範例中的套件指令清單 XML 和程式碼片段。 此範例是 UWP 應用程式,但範例中示範的功能也適用於已封裝的桌面應用程式。 請遵循下列指示來開始使用範例:

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

應用程式延伸模組簡介

在 Windows 10 中,應用程式延伸模組提供的功能類似於其他平臺上的外掛程式和附加元件。 Windows 10 年度版(版本 1607,組建 10.0.14393)引進了應用程式延伸模組。

應用程式延伸模組是 UWP 應用程式或已封裝的桌面應用程式,其具有延伸模組宣告,可讓他們與主應用程式共用內容和部署事件。 擴充功能應用程式可以提供多個擴充功能。

因為應用程式延伸模組只是 UWP 應用程式或封裝的桌面應用程式,所以它們也可以是功能完整的應用程式、主機延伸模組,並提供擴充功能給其他應用程式,而不需要建立個別的應用程式套件。

當您建立應用程式延伸模組主機時,您可以建立一個機會來開發應用程式周圍的生態系統,讓其他開發人員可以透過您可能沒有預期或擁有資源的方式來增強您的應用程式。 請考慮 Microsoft Office 擴充功能、Visual Studio 擴充功能、瀏覽器擴充功能等。這些擴充功能能為應用程式提供更豐富的體驗,進一步超越其隨附的功能。 擴充功能可以增加您的應用程式的價值和壽命。

概括而言,若要設定應用程式延伸模組關聯性,我們需要:

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

讓我們透過檢視 數學擴充程式碼範例,來了解如何實作一個假想的計算器,您可以使用擴充功能將新的函數加入其中。 在 Microsoft Visual Studio 2019 中,從程式代碼範例載入 MathExtensionSample.sln

數學延伸模組程式代碼範例

宣告應用程式為擴充主機

應用程式藉由在其 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://...",以及 uap3IgnorableNamespaces。 這些都是必要的,因為我們使用uap3命名空間。

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

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

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

宣告應用程式為延伸模組

應用程式會藉由在其 <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://..." 行,以及 uap3中的 IgnorableNamespaces。 這些都是必要的,因為我們使用 uap3 命名空間。

<uap3:Extension Category="windows.appExtension"> 將此應用程式識別為延伸模組。

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

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

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

決定如何實作延伸模組。

關於應用程式延伸模組的組建 2016 工作階段示範如何使用主機與延伸模組之間共用的公用資料夾。 在此範例中,擴充功能是由儲存在主機叫用之公用資料夾中的 JavaScript 檔案所實作。 這種方法的優點是輕量型、不需要編譯,而且可以支援建立默認登陸頁面,以提供延伸模組的指示,以及主應用程式Microsoft市集頁面的連結。 如需詳細資訊,請參閱 組建 2016 應用程式延伸模組程式代碼範例。 具體來說,請參閱 InvertImageExtension 專案以及 InvokeLoad() 專案中的 ExtensionManager.cs 和

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

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

主應用程式服務代碼

以下是叫用擴充功能應用程式服務的主機程式代碼:

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

要注意的一件事是如何確定要呼叫的 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 中定義的屬性擷取服務的名稱:

ExtensionManager.cs中的 Update(),在MathExtensionHost專案中

...
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。

呼叫 App 服務時,還需要提供包含此 App 服務的套件的家族名稱。 幸運的是,應用程式延伸模組 API 會提供這一行取得的資訊:connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

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

應用程式服務會使用 ValueSet 來交換資訊。 身為主機的作者,您需要設計一個具有彈性的協定來與擴充功能進行通訊。 在程式代碼範例中,這表示考慮未來可能需要 1、2 或更多自變數的擴充功能。

在這裡範例中,自變數的通訊協定是 ValueSet,其中包含名為 'Arg' 的索引鍵值組 + 自變數編號,例如,Arg1Arg2。 主機會傳遞 ValueSet中的所有參數,而擴充功能會使用它所需的參數。 如果擴充功能能夠計算出結果,則主機會預期擴充功能傳回的 ValueSet 中包含一個名為 Result 的索引鍵,該索引鍵內含計算的值。 如果該索引鍵不存在,主機會假設擴充功能無法完成計算。

擴充功能應用程式服務代碼

在範例程式碼中,擴充功能的應用程式服務未實作為背景工作。 相反地,它會使用單一進程應用服務模型,讓 App Service 在裝載該服務的擴充應用程式的相同進程中執行。 這仍然是與主機應用程式不同的程式,可提供程式區隔的優點,同時避免擴充程式與實作 App Service 的背景進程之間的跨進程通訊,來獲得一些效能優點。 請參閱 ,以了解如何將應用程式服務轉換為在與其主應用程式相同的進程中執行。比較應用程式服務作為背景作業執行與在相同進程中的差異,請參考

系統在啟動 App Service 後會變更至 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擷取所需的值。 如果它可以進行計算,它會將結果放在名為 Result的索引鍵底下,放在傳回給主機的 ValueSet。 回想一下,根據定義此主機及其延伸模塊通訊的通訊協定,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 市集會以套件的形式傳遞擴充功能。 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 中),可讓程式代碼範例存取延伸模組的標識碼、描述、標誌和應用程式特定資訊,例如使用者是否已啟用延伸模組。

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

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

ExtensionManager 提供一組 Extension 實例,以便將擴充套件、其名稱、描述和標誌的數據繫結到 UI。 ExtensionsTab 頁面會繫結至此集合,並提供 UI 來啟用/停用延伸模組,以及移除它們。

延伸模組索引標籤範例 UI

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

卸載 UI

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

擴充主機和擴充功能通常不是相同解決方案的一部分。 在這種情況下,若要除錯主機和擴充功能:

  1. 在 Visual Studio 的一個實例中載入主專案。
  2. 在另一個Visual Studio實例中載入延伸模組。
  3. 在調試程式中啟動主機應用程式。
  4. 在調試程式中啟動擴充功能應用程式。 (如果您要部署延伸模組,而不是對其進行偵錯,若要測試主機的套件安裝事件,請改為執行 建置 > 部署解決方案

現在,您將能夠在主機和擴充中打上斷點。 如果您開始偵錯擴充功能應用程式本身,您會看到應用程式的空白視窗。 如果您不想看到空白視窗,您可以將延伸模組專案的偵錯設定變更為不要啟動應用程式,而是在啟動時進行偵錯(以滑鼠右鍵按一下延伸模組專案,[屬性]>[偵錯]> 選取 [不要啟動,但在程式啟動時偵錯我的代碼])。您仍然需要啟動偵錯(F5)延伸模組專案,但它會等待主程式啟用延伸模組,然後中斷點會在延伸模組中被觸發。

偵錯程式代碼範例

在程式代碼範例中,主機和擴充功能位於相同的解決方案中。 執行下列動作以偵錯:

  1. 確定 MathExtensionHost 是啟始專案(以滑鼠右鍵點擊 MathExtensionHost 專案,然後點擊 設定為啟始專案)。
  2. 請在 Invoke 專案的 ExtensionManager.cs 檔案中,將斷點放在 處。
  3. F5 來執行 MathExtensionHost 專案。
  4. OnAppServiceRequestReceived 專案的 App.xaml.cs 中將斷點放在
  5. 開始偵錯 MathExtension 專案(以滑鼠右鍵按兩下 MathExtension 專案,偵錯 > 啟動新的實例),這會部署它,並在主機中觸發套件安裝事件。
  6. MathExtensionHost 應用程式中,流覽至 [計算] 頁面,然後按兩下 [x^y 以啟動擴充功能。 Invoke() 斷點首先被觸發,您可以看到正在進行擴充功能應用程式服務的呼叫。 然後會觸發延伸模組中的 OnAppServiceRequestReceived() 方法,您可以看到 App Service 計算並返回結果。

對作為應用程式服務實作的擴充功能進行故障排除

如果您的延伸模組主機無法連線到延伸模組的 App Service,請確定 <uap:AppService Name="..."> 屬性符合您在 <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市集搜尋查詢,以顯示可與您的應用程式搭配使用的延伸模塊清單。
  • 請考慮如何通知使用者新增和移除擴充功能。 您可以在安裝新的擴充功能時建立通知,並邀請用戶啟用它。 擴充功能預設應停用,讓用戶能夠控制。

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

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

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

應用程式延伸模組是獨立的套件,而且可以是獨立應用程式。 它們不能對另一個應用程式有部署相依性。 選用套件需要主要套件,並且不能在沒有它的情況下執行。

遊戲的擴充套件會是選擇性套件的合適方案,因為它緊密連接至遊戲,無法獨立於遊戲執行,而且您可能不想讓生態系統內的任何開發者隨意建立擴充套件。

如果相同的遊戲具有可自訂的UI附加元件或主題,那麼應用程式擴充套件可能是不錯的選擇,因為提供擴充套件的應用程式可以獨立運行,而任何第三方都可以開發它們。

備註

本主題提供應用程式延伸模組的簡介。 要注意的重點是建立主機並將其標示為 Package.appxmanifest 檔案、建立延伸模組,並在 Package.appxmanifest 檔案中標記它,並決定如何實作擴充功能(例如 app Service、背景工作或其他方式),以定義主機如何與擴充功能通訊, 並使用 AppExtensions API 來存取和管理延伸模組。