Unity3D之调用WinRT组件

结论

目前如果想调用WinRT组件引入原生UWP APIs,还是要切换至Universal Windows Platform平台进行调用,在目前版本的Unity(2018.1)的Standalone平台下很难实现(若各位有方法请务必通知我,万分感谢)。

起因

最近在做的一个Unity游戏项目,目标平台是Windows 10 PC,为了创造更贴近win10原生系统的应用体验(微软小娜,Toast消息通知),需要做的一个事情就是打通Unity和Windows运行时的通道。

尝试一:直接作为UWP工程打包

该方案简单直接,也可以直接达到目的。在Unity选择Universal Windows Platform平台后,该工程会自动引用对应版本Win10 SDK下的所有Windows运行时组件(*.winmd)。同时缺点很明显,使得该应用的权限收到了严格限制,无法直接使用Win32 api,也无法接入大部分非托管SDK。

尝试二:引入动态链接库+UWP增强方案

建一个.Net类库(基于.Net Frameword或.Net Core),根据微软提供的桌面应用UWP增强方案,在该类库中引用Windows.WINMD等WinRT组件(其中最主要的就是Windows.Foundation.UniversalApiContract.winmd,该组件公开了绝大部分UWP API),并调用这些组件中的UWP API,然后将编译好的dll添加到Unity3D的Plugins作为插件,接下来:

  • 直接打包: 这样是无法成功的,原因是Unity3D使用的Mono在生成最终程序集的时候要比原生的.Net更加严格,Unity在编译时不但检查本身Assembly-CSharp程序集所引用的程序集是否存在,还检查引用的程序集引用的程序集是否存在(csc.exe不会这样检查),所以直接打包会报错missing assembly reference。
  • 添加引用后打包:那是不是把该动态链接库的引用都添加到Unity工程就就可以了呢,答案是否定的,我使用的版本(Unity2018.1)在打包时报错:Metadata file xxxx.winmd does not contain valid metadata… ,也就是Unity/Mono 表示这东西不包含合法的元数据,但其实WinRT组件是使用标准的.Net元数据格式ECMA-355描述api的,个人猜测当前使用的Mono版本仍然无法将托管dll和WinRT组件平等对待。
尝试三:Standalone(Mono)打包+桌面桥+反射+UWP增强

既然Unity/Mono无法直接打包,那利用反射将扩展的程序集作为资源动态加载并调用是否可行呢,答案是否定的。桌面桥无法将一个win32应用彻底转制为UWP应用,只能提供一种机制将win32应用打包(并跟踪该应用的一切修改注册表和其它路径文件的行为),转制后的应用仍然以原有形式运行并可以使用全部系统资源(没有权限限制)。
该方案行不通的本质原因是Mono运行时/.Net 运行时/.Net Core运行时三者的探测强命名程序集的方法有区别

  • Mono运行时无法探测GAC下的dll后缀和exe后缀的程序集,WinRT组件(*.winmd)对于Mono运行时是陌生的,即便强行反射WinRT组件并加载,其中的方法也无法调用,因为Windows提供的系统WinRT组件只包含元数据,不包含Native代码的实现,实现是在COM组件中实现的,也就是WinRT组件只在编译时有用(即Reference,而非Lib .Net SDK也是这样的),在执行时才由.Net Core去加载这些api相应的COM组件(我认为该过程不是通过探测程序集实现的,而是.Net Core内建的匹配机制,因为其目标不是程序集而是COM组件),桌面桥转制后该应用仍然时基于Mono运行时的,Mono运行时对上述的一切一无所知,它只将WinRT组件当作普通的程序集,故无法执行本过程(调用时无法找到对应方法的实现)。
  • .Net运行时可以探测到winmd类型的程序集,但是因为不支持UWP应用所以无法执行UWP API。所以桌面桥可以将基于.Net运行时的应用(wpf,winform等)封装到UWP的壳中,就可以让原应用调用一部分UWP API(不能调用基于Core Window的api)。
  • 只有.Net Core运行时并且打包作为UWP应用时,可以使用完整的UWP API集。
尝试四:Standalone(IL2CPP)打包+桌面桥+反射+UWP增强

既然发现了是Mono运行时的坑,那使用IL2CPP运行时是否可行呢,答案是否定的。因为IL2CPP运行时不支持所有的动态特性,即反射程序集的方法根本无法执行(调用时报错IL2CPP不支持Assmebly.Loadxxxx)。因为IL2CPP是在运行时将所有IL代码转化成c++并编译为本机代码,执行时直接执行本机代码已经没有程序集的概念了,故无法加载。(或许Unity可以将原先的反射代码编译映射为:加载程序集后动态IL2CPP编译再动态编译C++代码成本机代码)

待尝试五:Standalone打包+桌面桥+UWP扩展

前面使用的都是UWP增强方案,即添加WinRT组件的引用并直接调用,但是微软提供了一个更全面的桌面应用UWP扩展方案,笔者暂未尝试待补全。

思考:

因为Unity直接作为UWP工程打包的时候不适用Mono运行时,使用的.Net Core运行时(不存在WinRT组件加载问题)/IL2CPP运行时(可以直接添加引用),所以可以调用WinRT组件

你可能感兴趣的:(Unity3D,UWP)