Win10 apps in .NET - references

This is Part 4 of my "VB Win10 Apps" series. (Everything applies to C# as well, of course).


The only high-level takeaway about references in UWP apps is this: "everything continues to 'just work' without any problem" .


This blog-post goes into the technical details, only for folks who want to know. References have changed a lot in UWP as compared to Win8.1, and they're going to change a bit further in VS2015 RTM.


References passed to VB/C# compiler 

The C#/VB compilers will compile your app against:

(1)  .NET APIs from the .NET framework, called ".NET for Windows Store Apps" in the diagram above. You get a reference to these out-of-the-box when you do File>New. In RC, these are just the same .NETCore4.5.1 reference assemblies that were also used for Win8.1, and in addition to that some new API surface areas from System.Numerics.Vectors. In RTM, it will compile against .NETCore5 reference assemblies which will provide more API surface area.

(2) WinRT APIs from UWP, called "Windows Universal" in the diagram above. You get a reference to these out-of-the-box when you do File>New. These are defined in a load of winmd reference assemblies. To find them, read “C:\Program Files (x86)\Windows Kits\10\Platforms\UAP\10.0.10069.0\platform.xml”, which will direct you to the actual winmd files that can be found in subdirectories of “C:\Program Files (x86)\Windows Kits\10\References”.

(3) WinRT APIs from other Win10 platforms, e.g. "Microsoft Mobile Extension SDK" in the diagram above. You can manually add references to these from AddReference>WindowsUniversal>Extension. They are defined in a load of winmd reference assemblies. For instance, look in “C:\Program Files (x86)\Windows Kits\10\Extension SDKs\WindowsMobile\\SDKManifest.xml”. Note a UWP app can still run on Win10.Mobile even without having a reference to the Mobile Extension SDK. And it's possible (but weird) for an app to reference the Mobile Extension SDK but not be able to run on Win10.Mobile. The reference doesn't say anything about the platforms the app will run on. All it does is allow the compiler to allow you to invoke platform-specific APIs. It's up to you to "adaptively guard" such invocations. More on this in a future post.

(4) All other references such as direct DLL/winmd references, NuGet references Project2Project references, ExtensionSDK references.


For each kind of reference, the questions are (Q1) what happens at build-time after the C#/VB compiler has finished? are there any files copied app-locally into my appx? (Q2) what happens at runtime?


As regards (1) .NET APIs from the .NET Framework:

  • Q1: What happens at build-time? None of the reference assemblies are copied app-locally. But certain implementation assemblies are copied app-locally…

    • In Debug builds, all the .NET Core 5 implementation assemblies are copied app-locally to your assembly. These implementations are at least as large as the .NETCore4.5.1 surface area that you compiled against in VS2015RC. Also, the System.Numerics.Vectors assembly from your NuGet reference is copied.
    • In Release builds, all the .NET Core 5 implementation assemblies are copied to a temporary directory. Then the .NET Native toolchain takes all of their MSIL, plus all the MSIL from your exe, plus all the MSIL from (4) all other references, and “tree-shakes” it, and compiles it into a single native .DLL file. Note that in some cases the implementation assemblies used
      for Debug and Release builds are different.
  • Q2: What happens at runtime?

    • In Debug builds, the build process modifies your Appxmanifest to include a reference to the “Microsoft .NET Core Runtime Package”. This is an “appx framework”. Only Microsoft can
      create appx-frameworks. There are only two other appx-frameworks in existence: one called VCLibs (with the C runtime library), and one with WinJS. (actually, as you can see from the AddReference > WindowsUniversal dialog, there are quite a few versions of VCLibs). An appx framework is delivered to a Windows device by the Store for any app that needs it. It is basically a shared library. We have packaged up the CLR runtime as an appx-framework. Note that this is purely for debug builds. You need never (and should never) manually add a reference to this thing. Anyway, the build process creates a fake stub .exe, whose sole job is to call LoadLibrary() to load the CLR runtime out of that appx-framework --
      actually LoadPackageLibrary which is the winrt equivalent – and then have it load up the MSIL of your exe and the other dlls.
    • In Release builds, the .NET Native compiler has already turned all the MSIL of your app into a single native .DLL. This DLL has a dependency on VCLibs. So the build process silently modified your appxmanifest to include a dependency on VCLibs appx-framework. Also, the build process creates a fake stub .exe, whose sole job is to load the single monolithic DLL
      and call its entry point.


As regards (2/3) WinRT APIs from UWP and other platforms

  • Q0: what is a winmd file anyway? how does it work?
    • A winmd file contains metadata about WinRT types (i.e. types, methods, signatures, ...). The winmds that are produced by VB/C#/F# also contain MSIL implementation. A WinRT type is basically like a COM class. To discover which winmd file contains metadata for a given type, invoke RoResolveNamespace. The metadata also says what is the IID/CLSID for each type. To get an instance of a type, invoke RoActivateInstance or QueryInterface passing that IID/CLSID. The metadata also contains the method table, which is how the compiler knows how to access methods.
  • Q1: what happens at build-time, concerning all of these winmd references?
    • In Debug builds, the build system takes a copy of "C:\Program Files (x86)\Windows Kits\10\UnionMetadata\windows.winmd". This file is basically a union of every single winmd file from the "C:\Program Files (x86)\Windows Kits\10\References" directory. The build system places a copy of this union winmd locally into your Appx.
    • In Release builds, the .NET Native compiler reads the winmd file to discover all IID/CLSIDs and all method tables, and it compiles this straight into the resulting native code. The native code is what calls RoActivateInstance and QueryInterface. This is exactly the same as what C++/CX does.
  • Q2: what happens at runtime?
    • In Debug builds, when you do "New Windows.SomeType", the CLR internally calls RoResolveNamespace, which gives back the path of the app-local windows.winmd file. The CLR loads this metdata file to find the metadata of Windows.SomeType. It constructs a "Runtime Callable Wrapper" (RCW) which knows how to call RoActivateInstance and QueryInterface and so on.
    • In Release builds, as indicated, everything has been compiled straight into the native code of your app.

Why do we have all this rigmarole about the union metadata? It's to support adaptive apps. Imagine if you had a method like this:

Sub f()
    If ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons") Then
        AddHandler HardwareButtons.CameraPressed, Sub(s, e) Debug.WriteLine("TODO: camera!")
    End If
End Sub

The way the CLR works is that, at entry to method f, it has to learn about the types and signatures of everything that's mentioned by the method, even down code paths that it doesn't go down. Imagine if you tried to execute method "f" on a Win10.Desktop machine, which lacks this particular API. There is no winmd on the operating system itself that can tell the CLR about types like Windows.Phone.UI.Input.HardwareButtons. That's why the app has to carry this metadata locally. In debug builds it carries the metadata locally in its appx-local copy of the union windows.winmd file. In release builds it carries the metadata directly inline in its native code.

I've given this code snippet about "platform-adaptive code". But the exact same issues apply to version-adaptive code, e.g. if you want to light up adaptive code if running on UWP v2.0, but still be able to run on UWP v1.0 devices.


As regards (3/4) Extension SDKs 

There are two distinct kinds of ExtensionSDK:

  • Platform Extension SDKs. These are (3) in my list above. They are new to UWP. They are discussed in depth by Brent Rector in his talk at BUILD. When you add a reference to a Platform Extension SDK (e.g. to Mobile, or to IOT) it doesn’t say anything about where your app will run. All it says is that the compiler will be able to invoke platform-specific APIs.

    • Q1: are any files copied locally into my appx? – no, not from Platform Extension SDKs. These consist solely of compile-time winmd reference assemblies, and solely contain types under the Windows.* namespace.
    • Q2: what happens at runtime?
      • In Debug builds, well, every single individual contract .winmd in your “Windows Kits\10\References” directory has already been combined into that single huge monolithic “union windows.winmd” file which gets copied app-locally into your directory. This was explained above.
      • In Release builds, well, your app has already been compiled to native. This was also explained above..
  • Normal Extension SDKs. These are part of (4) in my list above. They include things like Telerik controls, and Sqlite.

    • Q1: are any files copied locally into my appx? – yes, typically all normal ExtensionSDKs copy implementation DLLs into your assembly.
    • Q2: what happens at runtime? … depends on the SDK!
  • Appx-framework ExtensionSDKs. There's a third kind of ExtensionSDK. These are only authored by Microsoft. The only ones are VCLibs, WinJS. (Also, due to a bug, in VS2015 RC you can see another one called ".NET CoreCLR Runtime"). There are a vehicle for making sure that your app’s appxmanifest will have a <PackageDependency> in it, so that on customer devices the appropriate appx-framework will be downloaded from the store onto the customer device. They also each contain a local copy of that appx-framework (in both debug and release flavors) so you can debug-deploy to a device and have it work without needing a store connection.

    • Note that the .NET CoreCLR Runtime is only every used for debug. It is never deployed onto a customer machine.