Under the Hood with Windows Universal Projects

One of the main themes coming out of the keynotes and content at this year’s //Build 2014 conference was the continued convergence of the Windows Store and Windows Phone development platforms.  One exciting result of the fact that both these platforms now target very similar Windows Runtimes (WinRT and WinPRT, respectively) is the support for the new “Universal Apps” in Visual Studio 2013 Update 2.  (Another exciting result is that the “Programming the Windows Runtime by Example” book that Jeremy Likness and I wrote is even MORE relevant!) Projects created using the Universal App templates in Visual Studio include primary projects for each targeted platform as well as a shared project whose content “magically” gets included into each of the head projects.  Currently, only Windows 8.1 and Windows Phone 8.1 primary projects are supported, though apps targeting the Xbox One will one day be included, and Xamarin has indicated they’d be joining the fun soon. image
A Universal Apps Solution, showing Windows, Windows Phone, and Shared projects

I won’t spend a ton of time discussing the “how-to” aspect of Universal Apps projects – Jeff Prosise has published a series of blog posts that present the basics of working with those projects:

I want to focus on the mechanisms working behind-the-scenes that enables these things to work.  How does the Shared content make its way into the app projects in the solution, and why does it behave differently from other sharing mechanisms?  I find that understanding the inner workings of things leads to better decision making when employing them…perhaps that’s why I enjoy reading Jeff Richter’s books.

Where did they put the hood latch on this model?

So what is going on under the hood on these projects?  To understand that we need to get inside of the projects themselves.  Remember that most of the Visual Studio project files you work with (csproj, vbproj, etc) are actually MSBuild XML files.  You can examine this yourself by either opening the project files directly in a text editor (enhanced editors like Notepad++ or Sublime Text offer a better visual experience than good-old Notepad, but in a pinch, Notepad does work.)  You can also examine the inner content of a project file from Visual Studio itself with the following steps (or see here):

  • Right click on the project you want to work with in the Solution Explorer and select Unload Project
  • Right click on the project again and click Edit <ProjectName>

Regardless of what editor you choose to use, the content of your project files will follow the MSBuild Project File XML schema that is described here.  One of the jobs of the Visual Studio IDE is to properly maintain the various MSBuild schema elements as you add, remove, or rearrange items within your projects, but nothing prevents you from doing that manually, and there are actually a few MSBuild “tricks” that you can take advantage of that aren’t provided by the Visual Studio IDE.  Keith Rome has a blog post covering Wildcard Inclusions in C# Project Files that takes specific advantage of this capability.

Pay no attention to that man behind the curtain!

With one of the application project files open for XML editing, you can start to explore the magic of the Shared project.  Scroll down to the bottom of the file and locate the Import element that includes the attribute ‘Label = “Shared”’”:

<Import Project="..\UnderTheHood.Shared\UnderTheHood.Shared.projitems" 
Label="Shared" />

The purpose of the Import Element is to bring the contents of one project file into another project file, and a quick check of the MSDN docs suggests that it’s been around since at least .NET 2.0!  In this case, it is bringing in the contents of a file with a projitems extension from the shared project’s directory.  Note however that this file is NOT the shproj file that defines the Shared Project! (What gives?!) image
The Shared project extension is actually NOT what is referenced in the project files’ Import Element!

NOTE: You may also want to notice that there’s a Root Namespace property.  Setting this value determines the namespace that is given by default to new code files created within this shared project.

The projitems file is what the shared project creates/maintains.  If you open a File Explorer window to the Shared project file location, you will find this file.  Opening the file in a text editor (you can’t open it in Visual Studio while the shproj file is loaded, since it is already being referenced) will show that it is just another MSBuild file that includes the files that have been added to it  (if your text editor supports change notifications, add a new file to the Shared project while the projitems file is opened in the text editor and notice that when you save the project, the projitems file will be updated.)

So as you add, remove, or manipulate items in the shared project, the contents of the projitems file is updated, and this file’s contents in turn are simply incorporated into your app project files via the Import elements in their MSBuild XML.  This is why you can use #if declarations that are scoped to one project or another to differentiate behavior.

But wait, there’s (partially) more!

Remember that there’s more that you can do to affect your shared files’ integration behavior than simply throwing a bunch of #if and #else blocks throughout the shared code (ew!).  Don’t forget about partial classes and their often-overlooked cousins, partial methods. Partial methods first came on to the scene as a tool to allow developers to introduce code that strategically extended designer-generated files without having to worry about designer regeneration obliterating their code without having to create subclasses and override virtual methods.  When a partial class includes a partial method, private methods can call into that partial method, and partial extensions can choose whether or not to provide an implementation of the partial method. Wait…what?  Perhaps an example. I’ve added a new class to the shared project:

namespace UnderTheHood
{
    public partial class PartialClass
    {
        public void DoSomethingInteresting()
        {
            DoSomethingPlatformSpecific();
        }

        partial void DoSomethingPlatformSpecific();
    }
}

Note the lack of implementation for the DoSomethingPlatformSpecific method. Now in the Windows 8.1 project I’ve provided an implementation of the DoSomethingPlatformSpecific method:

namespace UnderTheHood
{
    public partial class PartialClass
    {
        partial void DoSomethingPlatformSpecific()
        {
            Debug.WriteLine("I am a Windows 8.1 implementation.");
        }
    }
}

And a slightly different implementation in the Windows Phone 8.1 project:

namespace UnderTheHood
{
    public partial class PartialClass
    {
        partial void DoSomethingPlatformSpecific()
        {
            Debug.WriteLine("I am a Windows Phone 8.1 implementation.");
        }
    }
}

Now calling the public DoSomethingInteresting method will call into whatever platform-specific DoSomethingPlatformSpecific implementation is in the current project, allowing for platform-specific customizations to occur in the platform-specific projects, rather than mixing them in the shared projects with #if and #else statements.  If new platforms are added to the Universal Project “universe” (sorry), you don’t have to worry about the #if statements becoming these big messes that include a lot of code that is greyed out by Visual Studio depending on which project it thinks is currently in scope. Bear in mind that partial methods differ from abstract methods in that they do not have to be overridden/implemented.  If no implementation is provided, the call into the partial method won’t fail or cause any negative side effects…it is simply a no-op.  As a consequence, there are a few rules to follow when working with partial methods:

  • They must return void (if they had return types, an implementation would be required)
  • That can have ref parameters, but not out parameters (if no implementation is provided, the ref parameter is unmodified)
  • Partial methods are inherently private in scope.
  • They can be static, and can be generic.
  • Delegates can only refer to implementations of static methods.

The bottom line

So hopefully now you have a better understanding of how these magical new shared projects work in Visual Studio.  Much of the technology they use has been in the DNA of our project files for a while.  Visual Studio 2013 Update 2 has been enhanced to include some nice new IDE updates in order to surface these features in a way that makes them very useful for writing reusable code.  This new compile-time reusability combines quite nicely with the post-compile reusability that is offered by Portable Class Libraries. If you want to learn more about them, Jeff Prosise will be delivering a session on universal apps at the Software Design & Development conference in London next month. The talk will delve into what universal apps are and how they’re structured, and he promises to have plenty of samples to share as he goes over the ins and outs of writing apps that target separate but similar run-times.  You might also benefit from watching Jeff’s video entitled Introduction to WinRT on WintellectNOW. If you’re not already a subscriber, you can sign up with the promo code Garland-13 and enjoy two weeks of access for free. Also, did I happen to already mention that Jeremy Likness and I had written a book about using the Windows Runtime?

Advertisements