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

实践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的方式开启)然后变更如下所示
    
    
    
    
      
    
      
    
      
        OptionalPackage
        Richie
        AssetsStoreLogo.png
      
    
      
        
        
      
    
      
        
      
    
      
        
          
            
            
          
        
      
    
这边说明一下有几个重要的修改区块! 先修改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 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]
"..CodeOnlyPackageCodeOnlyPackage.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(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 的新技术拉~