23 March 2016

.NET Native, SilverlightSerializer, MakeGenericType and MissingMetadataException

Recently I have ported my app Map Mania to the  Universal Windows Platform, and at the moment of this writing it is being certified. (that, I least I hope). It’s the first UWP app I actually submit to the Store. I have been doing quite some IoT Core stuff and that does not require much submitting. But it had to happen at one time, if only to dogfood my WpWinNl port to UWP, and it was an educating experience.

I learned the hard way that stuff that does work with debug settings, does not necessarily work when you compile stuff ‘for real’. I ran into quite a serious issue with generics. As soon as I turned my app over to the .NET Native toolchain, it even crashed on startup. Mind you, most of the ‘business code’ in this app and large parts of the XAML were still the same as the original Windows Phone7 and 8 app, as was the WpWinNl base library – all ported to UWP, but largely unchanged.

The root cause of the problem turned out to be SilverlightSerializer. This is a serialization framework made by Mike Talbot that I have been using to store app state since 2011. It’s name quite dates it origins. Unfortunately the link to the original article is dead – actually the whole blog seems to have disappeared - but it’s code has been sitting in WpWinNl and it’s predecessors ever since that time. I have ported it to Windows Phone 8, Windows RT, and now to UWP.

Deep down in the core it uses the Type.MakeGenericType method to make a generic type with two type parameters to store types. So if I have ViewModel with a double property, it makes a GetSetGeneric<ViewModel, double>. This, now, does not go down well with .NET Native. Runtime, it will pop up the following error:

Exception thrown: 'System.Reflection.MissingMetadataException' in System.Private.CoreLib.dll

Additional information: Reflection_InsufficientMetadata_EdbNeeded: WpWinNl.Utilities.GetSetGeneric<BeautyOfMaps.Models.TileMapSource,System.Boolean>. For more information, visit http://go.microsoft.com/fwlink/?LinkId=623485

Now, in your app, in  the “Properties” folder, there is a Default.rd.xml file that allows you to instruct the compiler not to ‘optimize things away’, to make sure this code still works. The insidious part is that this code is in my libary, downloaded from NuGet, and since it uses generics there is no ‘'generic’ way for me to instruct the compiler in the libary’s rd.xml not to optimize away code in the app that is using it.

If I wanted to fix this particular problem, I have to add the following line to the app’s rd.xml:

<Type Name="WpWinNl.Utilities.GetSetGeneric{BeautyOfMaps.Models.ViewModel.MainViewModel,System.Boolean}" Dynamic="Required All"/>

And guess what – then you get the next error. Now it it’s complaining about missing

WpWinNl.Utilities.GetSetGeneric<LocalJoost.Maps.TileSources.Google,
LocalJoost.Maps.TileSources.GoogleType>

So it apparently needs a line like this for every class type and property type combination that goes past MakeGenericType. So while you already defined in code what these properties are, you have to declare (again) in XML which ones you want to keep. I have been in contact with the .NET Native team about the apparent lack of logic behind this, but is seems that according to their date very little people actually use this kind of reflection. While this is true without any doubt – Microsoft telemetry does not lie - I think the critical thinking error that was made here is that those people might be using libraries that do – like mine! - and then they are thoroughly in deep… er, manure.

Trying to add manually all class/property type combinations is where madness lies, my friends. I came to about the 6th type and then I was kind of done. I am a developer, so I am lazy by nature, and in stead of actually doing it manually I added a weird property to SilverlightSerializer called RdXmlEntries. It’s usage is as follows:

  • Run the app in DEBUG (so not .NET Native)
  • Serialize all objects you want to serialize
  • Directly behind the last serialization, add this code:
foreach (var entry in SilverlightSerializer.RdXmlEntries)
{
  Debug.WriteLine(entry);
}
  • Copy and paste the resulting data in your Default.rd.xml
  • Verify your code now runs under .NET Native.

This code is now in WpWinNlBasic version 3.0.5-alpha on NuGet and available for use. You now know why I keep this in the unstable branch – as long as I haven’t had the chance to dogfood all the stuff I have ported and created earlier, I want to thread carefully.

When I showed this solution to the .NET Native team I got feedback that I only needed to provide lines for when the second parameter is a value type, not when it’s a reference type. I have validated that in one case, but I serialize quite a complicated object graph and had spent more than enough time on it already, I did not really feel like experimenting further.

Bottom line – be very, very careful with advanced reflection in UWP apps. Don’t make the same mistake I made – test early under .NET Native and follow guidance provided here to maintain your sanity.

Special thanks to Tom Verhoeff for providing assistance based upon his earlier experience. And sorry, no code sample this time as this is more general guidance that a specific coding issue ;)

No comments: