Following the Unity announcement about deprecating the .NET backend I have been slowly turning my attention towards using IL2CPP instead which some time in the future will be the only option for Unity HoloLens development. Of course, there are supported LTS versions but I guess it will often be the case that as frameworks and SDKs move forwards they would tend towards supporting newer features and functionality. Either way, as a HoloLens dev it wouldn’t help to at least be prepared.
Just to give a very high-level description of what this means; using the .NET backend generates a .NET UWP project when building my Unity project for HoloLens. This means debugging C# code in Visual Studio and deploying a .NET (or .NET native) app to a HoloLens device. When I build an IL2CPP project in Unity it creates a native C++ Visual Studio project which is generated from the C# that you write in your Unity scripts. So effectively converts .NET code into native C++.
There is a managed debugger so you can continue to work with C# in a debugging experience. In the Unity build settings if you check ‘Wait for managed Debugger’ then
when you run the resulting app on the HoloLens it will put up a dialog which will wait giving you a chance to hook up the managed debugger.
I usually open two instances of Visual Studio; one with the native code and from the other choose the menu option Debug > Attach Unity Debugger and use that to debug C# code
I can then set breakpoints in my C# scripts as expected. Over the last few Unity versions I have been using this experience has been steadily improving. It seemed initially to be slow and sometimes the debugger wouldn’t catch my first-chance exceptions. This works well in 2018.3.0f2 though which I am currently using.
I was working with a sample that I had previously written using the Microsoft Auth Library which was originally used as an example of delegated auth on HoloLens but I recently extended to also show ‘device code flow’ which allows the auth to happen on a second device which may be more convenient if typing passwords or codes is required.
In order to use the MSAL library I downloaded the Nuget package directly from the Nuget website and then chose the relevant dll to include directly into my Unity project. The MSAL library is a .NET library so you may be wondering how this works with IL2CPP. So, the .NET assembly will get converted into C++ which is included in the resulting project.
The device code auth flow works ok in the Unity editor since it doesn’t have the complication of requiring a browser to be present in the app. It also worked using the .NET backend but when I switched over to IL2CPP things stopped working and I was hit with a Exception in the managed debugger.
Error on deserializing read-only members in the class: No set method for property ‘ErrorDesription’ in type ‘Microsoft.Identity.Core.Oauth2.Oauth2ResponseBase’
Decompiling the original assembly revealed that there was a setter for that property so where’d it go? As it turns out, the IL2CPP process will strip out any code that it detects to be unused, i.e. not referenced elsewhere. This results in less code, faster build times, etc. Detecting unused code that may actually be used by reflection is tricky though and this code can get stripped which is exactly why I got the exception above. No worries though, since it’s not a huge leap to diagnose and even easier to fix. The Unity forum staff pointed me at the link.xml file, which if you create one in your Assets folder can enable you to take some control over which code gets stripped. Adding the following xml prevents all code stripping from the auth library and fixes my first issue:
Now, my sample still didn’t work as this time I was getting a NullReferenceException in the managed debugger. This didn’t reveal any clues as to what the problem was so I was forced to turn towards the native debugger and the generated C++. There are some great tips here on navigating the generated code, catching exceptions and viewing strings, etc. So, I spent a bit of time stepping through the call stack and I wouldn’t recommend it as an experience as it is fairly verbose and easy to get lost. I then turned on first chance exceptions and discovered where the exception was being thrown.
Now, this is C++ code generated from System.Runtime.Serialization and more specifically this function System.Runtime.Serialization.Json.JsonFormatWriterInterpreter::TryWritePrimitive and it is using reflection itself to find a method that will be used in the implementation. Of course, that method has been stripped out and so this code fails. Adding an entry to the link.xml file for System.Runtime.Serialization fixes this but leads me to think that as a consumer of this code I shouldn’t really be concerned with it’s internals in this way.
And finally I have a working sample which you can find here.