Unreal 中的 WinRT
在开发 HoloLens 的过程中,可能需要使用 WinRT 编写功能。 例如,在 HoloLens 应用程序中打开文件对话框需要 winrt/Windows.Storage.Pickers.h 标头文件中的 FileSavePicker。 Unreal 的生成系统从版本 4.26 开始支持 WinRT。
标准 WinRT API
使用 WinRT 最常见和最简单的方法是从 WinSDK 中调用方法。 为此,请打开 YourModule.Build.cs 文件并添加以下行:
if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.HoloLens)
{
// These parameters are mandatory for winrt support
bEnableExceptions = true;
bUseUnity = false;
CppStandard = CppStandardVersion.Cpp17;
PublicSystemLibraries.AddRange(new string[] { "shlwapi.lib", "runtimeobject.lib" });
PrivateIncludePaths.Add(Path.Combine(Target.WindowsPlatform.WindowsSdkDir,
"Include",
Target.WindowsPlatform.WindowsSdkVersion,
"cppwinrt"));
}
接下来,需要添加以下 WinRT 标头:
#if (PLATFORM_WINDOWS || PLATFORM_HOLOLENS)
//Before writing any code, you need to disable common warnings in WinRT headers
#pragma warning(disable : 5205 4265 4268 4946)
#include "Windows/AllowWindowsPlatformTypes.h"
#include "Windows/AllowWindowsPlatformAtomics.h"
#include "Windows/PreWindowsApi.h"
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Perception.Spatial.h>
#include <winrt/Windows.Foundation.Collections.h>
#include "Windows/PostWindowsApi.h"
#include "Windows/HideWindowsPlatformAtomics.h"
#include "Windows/HideWindowsPlatformTypes.h"
#endif
WinRT 代码只能在 Win64 和 HoloLens 平台中进行编译,因此 if 语句会防止将 WinRT 库包含在其他平台上。 因为需要 IUnknown 接口,所以添加了 unknwn.h。
NuGet 包中的 WinRT
如果需要添加支持 WinRT 的 NuGet 包,则会稍微复杂一些。 在这种情况下,Visual Studio 可以完成几乎所有工作,但 Unreal 生成系统不能。 还好这并不是很困难。 下面是有关如何下载 Microsoft.MixedReality.QR 包的一个示例。 可以将其替换为其他包,只需确保自己不会丢失 winmd 文件并复制正确的 DLL 即可。
前面部分中 Windows SDK DLL 由 OS 进行处理。 NuGet 的 DLL 必须由模块中的代码进行管理。 建议添加代码来下载这些 DLL,将其复制到二进制文件夹,以及在模块启动时加载到进程内存。
第一步,应将 packages.config (/nuget/reference/packages-config) 添加到模块的根文件夹中。 应在此处添加要下载的所有包,包括这些包的所有依赖项。 此处我添加了 Microsoft.MixedReality.QR 作为主要有效负载,并对其添加了另外两个作为依赖项的包。 该文件的格式与 Visual Studio 中的格式相同:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.MixedReality.QR" version="0.5.2102" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.6" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>
现在,可以下载 NuGet、所需的包,或参阅 NuGet 文档。
打开 YourModule.Build.cs 并添加以下代码:
// WinRT with Nuget support
if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.HoloLens)
{
// these parameters mandatory for winrt support
bEnableExceptions = true;
bUseUnity = false;
CppStandard = CppStandardVersion.Cpp17;
PublicSystemLibraries.AddRange(new string [] { "shlwapi.lib", "runtimeobject.lib" });
// prepare everything for nuget
string MyModuleName = GetType().Name;
string NugetFolder = Path.Combine(PluginDirectory, "Intermediate", "Nuget", MyModuleName);
Directory.CreateDirectory(NugetFolder);
string BinariesSubFolder = Path.Combine("Binaries", "ThirdParty", Target.Type.ToString(), Target.Platform.ToString(), Target.Architecture);
PrivateDefinitions.Add(string.Format("THIRDPARTY_BINARY_SUBFOLDER=\"{0}\"", BinariesSubFolder.Replace(@"\", @"\\")));
string BinariesFolder = Path.Combine(PluginDirectory, BinariesSubFolder);
Directory.CreateDirectory(BinariesFolder);
ExternalDependencies.Add("packages.config");
// download nuget
string NugetExe = Path.Combine(NugetFolder, "nuget.exe");
if (!File.Exists(NugetExe))
{
using (System.Net.WebClient myWebClient = new System.Net.WebClient())
{
// we aren't focusing on a specific nuget version, we can use any of them but the latest one is preferable
myWebClient.DownloadFile(@"https://dist.nuget.org/win-x86-commandline/latest/nuget.exe", NugetExe);
}
}
// run nuget to update the packages
{
var StartInfo = new System.Diagnostics.ProcessStartInfo(NugetExe, string.Format("install \"{0}\" -OutputDirectory \"{1}\"", Path.Combine(ModuleDirectory, "packages.config"), NugetFolder));
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
var ExitCode = Utils.RunLocalProcessAndPrintfOutput(StartInfo);
if (ExitCode < 0)
{
throw new BuildException("Failed to get nuget packages. See log for details.");
}
}
// get list of the installed packages, that's needed because the code should get particular versions of the installed packages
string[] InstalledPackages = Utils.RunLocalProcessAndReturnStdOut(NugetExe, string.Format("list -Source \"{0}\"", NugetFolder)).Split(new char[] { '\r', '\n' });
// winmd files of the packages
List<string> WinMDFiles = new List<string>();
// WinRT lib for some job
string QRPackage = InstalledPackages.FirstOrDefault(x => x.StartsWith("Microsoft.MixedReality.QR"));
if (!string.IsNullOrEmpty(QRPackage))
{
string QRFolderName = QRPackage.Replace(" ", ".");
// copying dll and winmd binaries to our local binaries folder
// !!!!! please make sure that you use the path of file! Unreal can't do it for you !!!!!
string WinMDFile = Path.Combine(NugetFolder, QRFolderName, @"lib\uap10.0.18362\Microsoft.MixedReality.QR.winmd");
SafeCopy(WinMDFile, Path.Combine(BinariesFolder, "Microsoft.MixedReality.QR.winmd"));
SafeCopy(Path.Combine(NugetFolder, QRFolderName, string.Format(@"runtimes\win10-{0}\native\Microsoft.MixedReality.QR.dll", Target.WindowsPlatform.Architecture.ToString())),
Path.Combine(BinariesFolder, "Microsoft.MixedReality.QR.dll"));
// also both both binaries must be in RuntimeDependencies, unless you get failures in Hololens platform
RuntimeDependencies.Add(Path.Combine(BinariesFolder, "Microsoft.MixedReality.QR.dll"));
RuntimeDependencies.Add(Path.Combine(BinariesFolder, "Microsoft.MixedReality.QR.winmd"));
//add winmd file to the list for further processing using cppwinrt.exe
WinMDFiles.Add(WinMDFile);
}
if (Target.Platform == UnrealTargetPlatform.Win64)
{
// Microsoft.VCRTForwarders.140 is needed to run WinRT dlls in Win64 platforms
string VCRTForwardersPackage = InstalledPackages.FirstOrDefault(x => x.StartsWith("Microsoft.VCRTForwarders.140"));
if (!string.IsNullOrEmpty(VCRTForwardersPackage))
{
string VCRTForwardersName = VCRTForwardersPackage.Replace(" ", ".");
foreach (var Dll in Directory.EnumerateFiles(Path.Combine(NugetFolder, VCRTForwardersName, "runtimes/win10-x64/native/release"), "*_app.dll"))
{
string newDll = Path.Combine(BinariesFolder, Path.GetFileName(Dll));
SafeCopy(Dll, newDll);
RuntimeDependencies.Add(newDll);
}
}
}
// get WinRT package
string CppWinRTPackage = InstalledPackages.FirstOrDefault(x => x.StartsWith("Microsoft.Windows.CppWinRT"));
if (!string.IsNullOrEmpty(CppWinRTPackage))
{
string CppWinRTName = CppWinRTPackage.Replace(" ", ".");
string CppWinRTExe = Path.Combine(NugetFolder, CppWinRTName, "bin", "cppwinrt.exe");
string CppWinRTFolder = Path.Combine(PluginDirectory, "Intermediate", CppWinRTName, MyModuleName);
Directory.CreateDirectory(CppWinRTFolder);
// all downloaded winmd file with WinSDK to be processed by cppwinrt.exe
var WinMDFilesStringbuilder = new System.Text.StringBuilder();
foreach (var winmd in WinMDFiles)
{
WinMDFilesStringbuilder.Append(" -input \"");
WinMDFilesStringbuilder.Append(winmd);
WinMDFilesStringbuilder.Append("\"");
}
// generate winrt headers and add them into include paths
var StartInfo = new System.Diagnostics.ProcessStartInfo(CppWinRTExe, string.Format("{0} -input \"{1}\" -output \"{2}\"", WinMDFilesStringbuilder, Target.WindowsPlatform.WindowsSdkVersion, CppWinRTFolder));
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
var ExitCode = Utils.RunLocalProcessAndPrintfOutput(StartInfo);
if (ExitCode < 0)
{
throw new BuildException("Failed to get generate WinRT headers. See log for details.");
}
PrivateIncludePaths.Add(CppWinRTFolder);
}
else
{
// fall back to default WinSDK headers if no winrt package in our list
PrivateIncludePaths.Add(Path.Combine(Target.WindowsPlatform.WindowsSdkDir, "Include", Target.WindowsPlatform.WindowsSdkVersion, "cppwinrt"));
}
}
需按如下所示定义 SafeCopy 方法:
private void SafeCopy(string source, string destination)
{
if(!File.Exists(source))
{
Log.TraceError("Class {0} can't find {1} file for copying", this.GetType().Name, source);
return;
}
try
{
File.Copy(source, destination, true);
}
catch(IOException ex)
{
Log.TraceWarning("Failed to copy {0} to {1} with exception: {2}", source, destination, ex.Message);
if (!File.Exists(destination))
{
Log.TraceError("Destination file {0} does not exist", destination);
return;
}
Log.TraceWarning("Destination file {0} already existed and is probably in use. The old file will be used for the runtime dependency. This may happen when packaging a Win64 exe from the editor.", destination);
}
}
需要手动将 NuGet DLL 加载到 Win32 进程内存中;建议将手动加载操作添加到模块的启动方法中:
void StartupModule() override
{
#if PLATFORM_WINDOWS
const FString LibrariesDir = FPaths::ProjectPluginsDir() / "MyModule" / THIRDPARTY_BINARY_SUBFOLDER;
FPlatformProcess::PushDllDirectory(*LibrariesDir);
const FString DllName = "Microsoft.MixedReality.QR.dll";
if (!FPlatformProcess::GetDllHandle(*DllName))
{
UE_LOG(LogHMD, Warning, TEXT("Dll \'%s\' can't be loaded from \'%s\'"), *DllName, *LibrariesDir);
}
FPlatformProcess::PopDllDirectory(*LibrariesDir);
#endif
}
最后,可将 WinRT 标头包含在代码中,如前一部分中所述。
下一个开发检查点
如果你遵循我们规划的 Unreal 开发历程,则你处于探索混合现实平台功能和 API 的过程之中。 从这里,你可以继续了解任何主题或直接跳到在设备或仿真器上部署应用。