WinRT no Unreal
Ao longo do desenvolvimento do HoloLens, talvez seja necessário escrever um recurso usando o WinRT. Por exemplo, abrir um diálogo de arquivo em um aplicativo HoloLens precisaria do FileSavePicker no arquivo de cabeçalho winrt/Windows.Storage.Pickers.h. O WinRT tem suporte no sistema de build do Unreal da versão 4.26 em diante.
As APIs padrão do WinRT
A maneira mais comum e fácil de usar o WinRT é chamar métodos do WinSDK. Para fazer isso, abra o arquivo YourModule.Build.cs e adicione as seguintes linhas:
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"));
}
Em seguida, você precisa adicionar os seguintes cabeçalhos do 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
O código WinRT só pode ser compilado nas plataformas Win64 e HoloLens, portanto, a instrução if impede que bibliotecas WinRT sejam incluídas em outras plataformas. unknwn.h foi adicionado para ter a interface IUnknown.
WinRT de um pacote NuGet
É um pouco mais complicado se você precisar adicionar um pacote NuGet com suporte ao WinRT. Nesse caso, o Visual Studio pode fazer praticamente todo o trabalho para você, mas o sistema de build do Unreal não pode. Felizmente, não é muito difícil. Veja abaixo um exemplo de como baixar o pacote Microsoft.MixedReality.QR. Você pode substituí-lo por outro, apenas certifique-se de não perder o arquivo winmd e copiar a dll correta.
As dlls do SDK do Windows da seção anterior são tratadas pelo sistema operacional. As dlls do NuGet devem ser gerenciadas pelo código em seu módulo. É recomendável adicionar código para baixá-los, copiar para a pasta binários e carregar na memória do processo na inicialização do módulo.
Na primeira etapa, você deve adicionar um packages.config (/nuget/reference/packages-config) à pasta raiz do módulo. Lá, você deve adicionar todos os pacotes que deseja baixar, incluindo todas as dependências. Aqui, eu adiciono Microsoft.MixedReality.QR como um conteúdo primário e outros dois como dependências para ele. O formato desse arquivo é o mesmo do 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>
Agora você pode baixar o NuGet, os pacotes necessários ou consultar a documentação do NuGet.
Abra YourModule.Build.cs e adicione o seguinte código:
// 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"));
}
}
Você precisará definir o método SafeCopy da seguinte maneira:
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);
}
}
As DLLs do NuGet precisam ser carregadas na memória do processo do Win32 manualmente; É recomendável adicionar o carregamento manual ao método de inicialização do módulo:
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
}
Por fim, você pode incluir cabeçalhos WinRT em seu código, conforme descrito na seção anterior.
Próximo ponto de verificação de desenvolvimento
Se está seguindo o percurso de desenvolvimento do Unreal que apresentamos, você está no meio da exploração de funcionalidades e APIs da plataforma de Realidade Misturada. A partir daqui, você pode continuar para qualquer tópico ou ir diretamente para implantar seu aplicativo em um dispositivo ou emulador.