Windows 10 UWP 29 of N: Build 2017 Optional Package and Releative set package with UWP

  • 672
  • 0
  • UAP
  • 2021-04-30

實作Optional Package, Relative set在UWP的平台上,是Extension的一種喔!

概觀

先前UWP支援的Extension的方式有如下

  1. App protocol
  2. App service
  3. App extension

在App extension的部分可以執行某部分的Code(Javascript)的方式把WebView動態的載入並且執行。但是沒辦法動態載入其他code來動態觸發程式碼!到了Creator update支援了一個新特性!就是Optional package,這個功能最大特性就是可以動態執行C++的Code!


實際Demo-載入資源

今年的Build上有介紹了很多強大的功能,今天要介紹的叫做Optional Package(AKA Downloaded Content)所以基本上也就是DLC的概念!

那麼我們直接來看看如何實作這功能八

操作環境:

  • Windows 10 Creator update lastest build
  • Visual Studio 2017 lastest build

 

  1. 建立UWP專案並作為MainApp,這邊我一樣使用C#的Blank的範本來建立。
  2. 然後再建立另一個UWP專案命名為OptionalPackage,但是使用的C++的範本喔(以目前的UWP結構只能使用C++的專案來建立Optional Package)
  3. Soultion結構大致如下 
  4. 建立好C++的UWP空白專案後,編輯該專案的Package.appxManifest檔案(一樣使用XML的方式開啟)然後變更如下所示
    <?xml version="1.0" encoding="utf-8"?>
    
    <Package
      xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
      xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
      xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
      xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
      IgnorableNamespaces="uap mp uap3">
    
      <Identity
        Name="af0a3f05-3184-4836-8d83-130539002470"
        Publisher="CN=Richie"
        Version="1.0.0.0" />
    
      <mp:PhoneIdentity PhoneProductId="af0a3f05-3184-4836-8d83-130539002470" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
    
      <Properties>
        <DisplayName>OptionalPackage</DisplayName>
        <PublisherDisplayName>Richie</PublisherDisplayName>
        <Logo>Assets\StoreLogo.png</Logo>
      </Properties>
    
      <Dependencies>
        <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.15063.0" MaxVersionTested="10.0.15063.0" />
        <uap3:MainPackageDependency Name="f3b56e8b-6e65-498a-b020-b6f319ae4c09"/>
      </Dependencies>
    
      <Resources>
        <Resource Language="x-generate"/>
      </Resources>
    
      <Applications>
        <Application Id="SampleOptional"
          Executable="$targetnametoken$.exe"
          EntryPoint="OptionalPackage.SampleOptional">
          <uap:VisualElements
            DisplayName="OptionalPackage"
            Square150x150Logo="Assets\Square150x150Logo.png"
            Square44x44Logo="Assets\Square44x44Logo.png"
            Description="OptionalPackage"
            BackgroundColor="transparent">
            <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
            <uap:SplashScreen Image="Assets\SplashScreen.png" />
          </uap:VisualElements>
        </Application>
      </Applications>
    </Package>
這邊說明一下有幾個重要的修改區塊! 先修改MinVersion以及MaxVersionTested為10.0.15063.0也就是Creator update;然後再加入uap3的namespacce然後再Dependencies的Node中加入uap3:MainPackageDependency 然後裡面的Name的值就是放MainApp在Package.appxmanifest裡面Identity的Node中ID;接者再修改Application的Id變更為你想要的名稱(原本建立出來的Id的值會是App!);最後把Capabilites的節點刪除掉(因為Optional package會使用Host App的Capabilites在本範例中也就是MainApp的Package.appxmanifest的Capabilities的宣告)
Package.appxmanifest的變更如下圖(左邊為Optional package的;右邊則為MainApp的)

這樣大致就是把Optional package的設定調整完畢!先來測試看看吧~ 測試步驟會事把Host的App部屬到OS中再把OptionalPackage的App部屬進去! 

如果成功部屬可以使用Powershell的指令來看看Host App得狀態

get-appxpackage "放入App的ID"

執行結果如下

可以看到Dependencies的部分有多了一串GUID,那個就是代表Optional Package的ID喔!這樣就算是成功的把MainApp跟Optional Package的APP綁定再一起。

接者來看看要怎樣把Optional package內的 SampleFile.text 的內容在MainApp中讀取。

​
private async Task<string> LoadAssetsAsync()
        {
            var assetsPackage = Package.Current.Dependencies.Where(package => package.IsOptional == true && package.Id.Name.Contains("SampleOptional")).FirstOrDefault();
            if (assetsPackage == null)
            {
                return string.Empty;
            }

            var fileText = string.Empty;
            var assetsFolder = assetsPackage.InstalledLocation;
            try
            {
                var textFile = await assetsFolder.GetFileAsync("SampleFile.txt");
                using (var fileStream = await textFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
                using (var reader = new DataReader(fileStream))
                {
                    reader.ByteOrder = ByteOrder.LittleEndian;
                    reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                    reader.InputStreamOptions = InputStreamOptions.Partial;
                    var loadCount = await reader.LoadAsync((uint)fileStream.Size);
                    while (reader.UnconsumedBufferLength > 0)
                    {
                        var str = reader.ReadString(loadCount);
                        fileText += str;
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
            return fileText;
        }



​

要從Package中去找到Dependencies中找尋看看是否是Optional和Id是否有比對成功,接著從該Optional package的InstalledLocation中去找到該文件並且讀取出來!

實際輸出畫面如下

在Setting(設定)的APP的列表中可以看到有部屬成功的Optional package!

Optional package app在Package.manifest內Application元素的Id屬性修改成不是App除了為了使該App變成Optional以外也將會變成附帶在MainApp的附屬參考container的形式,而Optional package如果只釋放資源檔案(文字檔、圖片、音樂、影片...等檔案)的方式並不會需要跟MainApp有關聯的連動性!

預設UWP的App會顯示在App list中,但調整成Optional package之後就應該是相依在MainApp作為開啟的行為,需要調整行為如下

如果需要讓Optional package不顯示在App list中只需要在 Package.appxmanifest 中的 Applications -> Application -> uap:VisualElements 的 AppListEntry 的屬性調整成 none就可以不顯示在App list中。

這樣只要調整成Optional package的UWP app就不會顯示在App list中。

 


實際Demo-執行程式碼

接者要Demo的是能夠讓UWP的APP在Runtime時期跑C++的程式碼!流程如下

  1. 建立一C++語言的UWP空白APP專案
  2. 建立一C++語言的UWP的Dll專案
  3. 將Dll專案之參考加入到UWP C++的專案中

接者在C++的Code中加入如下Sample code

.h

#pragma once
extern "C"
{
	__declspec(dllexport) int __cdecl GetAge();
}

.cpp

// Optional Package Code
// **NOTE** THIS IS A FAKE AGE GENERATOR
__declspec(dllexport) int __cdecl GetAge()
{
	int lowestAge = 1;
	int highestAge = 85;

	return rand() % ((lowestAge - highestAge) + 1) + lowestAge;
}

然後一樣將 C++的UWP空白專案轉換成Optional package(步驟如同上述的Asset optional package轉換流程,修改Package.appxmanifest檔案)

  1. 將UAP3宣告加入並且在 Dependencies的Element中加入uap3:MainPackageDependency並且將該Element的Name放入MainApp的ID
  2. 移除Capcabilities的宣告
  3. 視情況是否將uap:VisualElement內的AppListEntry變更成None
步驟一和二是建立成optional package必須步驟!第三步驟則是依照需求來決定是否需要。

接著在MainApp中加入一文字檔案並且命名為 Bundle.Mapping.txt ,並且加入以下文字

[OptionalProjects]
"..\CodeOnlyPackage\CodeOnlyPackage.vcxproj"  // 指向相對路徑的Visual C++ UWP optional package專案檔

這個設定就是要讓MainApp和CodeOnlyPackage的專案變成Bundle的形式進行編譯如下圖所示

編譯MainApp的時候Visual Studio 2017就會將Bundle.Mapping.txt內有加入OptionalProjects的專案一併進行編譯的動作!上圖的CodeOnlyPackage是Visual C++ 的UWP專案並調整成optional package而SampleDll專案則是Visual C++的DLL專案。

 

最後在MainApp需要使用到DllImport的機制來Load C++的Dll,如下C# Code

[DllImport("kernel32", EntryPoint = "LoadPackagedLibrary", SetLastError = true)]
static extern IntPtr LoadPackagedLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpFileName, int reserved = 0);

[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

使用的Method如下

delegate Int32 ExecuteAgeFunc();

private void ExecuteCodeAsync()
        {
            var codePackage = Package.Current.Dependencies.Where(package => package.IsOptional == true && package.Id.Name.Contains("CodePackage")).FirstOrDefault();
            if (codePackage == null)
            {
                return -1;
            }

            try
            {
                var hModule = LoadPackagedLibrary(DllFileName);
                if (hModule != IntPtr.Zero)
                {
                    var result = GetProcAddress(hModule, MethodName);
                    var methodResult = Marshal.GetDelegateForFunctionPointer<ExecuteAgeFunc>(result);
                    return methodResult.Invoke();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            return -1;
        }

這邊一樣先是從Package中尋找到Optional的Package並且搜尋你需要使用的package!然後使用LoadPackageLibrary去載入Dll,最後使用Marshal.GetDelegateForFunctionPointer去串接在Dll內的Method。如果有看過MSDN的Document ( https://msdn.microsoft.com/en-us/library/windows/desktop/hh447159(v=vs.85).aspx ) 就會發現在使用LoadPackageLibrary的功能在Phone device需要使用的DllImport使用的是PhoneAppModelHost.dll!在W10M的測試是可以使用Kernel32。

實際截圖

以上皆測試在Lumia 950XL!以及Windows 10 PC


結論

Optional package可以讓開發人員選擇是否某些資源要變成公用資源的形式可以跨不同的App使用(像是Assets optional package),而主體的App若是需要額外附加功能(Downloadable content)則是可以把optional package做成 load C++的方式。 開發人員可以藉此延伸App的特性讓App有額外的附加功能透過IAP(in-app-purchase)的方式購改額外的功能。

 

Update

如果要上架到Store上針對Enterprise的部分(LOB、Enterprise)不需要經過特別審核!但是針對一般開發人員需要申請才能上架optional package、releative set的app package喔~參考連結 https://docs.microsoft.com/en-us/windows/uwp/packaging/optional-packages

 

 

***以上Code以及說明都有可能隨著Windows 10 的版本以及Visual Studio 版本有所調整!***

參考資料 Microsoft Docs

下次再分享Windows 10 的新技術拉~