Tuesday, September 27, 2011

Build your first WinRT component

I think that the dust has cleared after the Build Windows conference which took place two weeks ago. Since then blog posts started to show up talking about Metro style apps, WinRT, its impact on current skills, etc. Also, Microsoft released public preview of Windows 8, so it is about a good time to give it a try. So I installed Win8 on the latest Virtual Box, and it seems to work fine, apart from a few systems crashes per hour ;) So in this post, I want to show you what it takes to build simple yet functional WinRT component in native C++, and also will talk why you would do that. But before we jump right to the code, I would like to provide a brief overview of Windows 8 developer platform, and WinRT itself.

Windows Runtime

Microsoft says that WinRT is “a solid, efficient foundation for the new Windows 8 developer platform”. I love such marketing slang! So what WinRT really is? It is a framework (calling it a framework is a bit unfair, since WinRT is a part of the core of Windows OS, and is compiled each time Windows is built) for building Metro style apps.

Metro style apps

This is important information. It means WinRT can only be used for developing metro style apps. Metro style apps, although perfectly legal on a desktop system, are especially meant for tablets. This is the environment where they truly shine! And tablets are the most important reason Microsoft has actually created it. Why ? How else would they provided fast and fluid user experience on an ARM based tabled cloacked at 1.6 GHz ? Would you run fully blown WPF apps there ? I doubt. Silverlight ? Better, but you can’t code it with C++. To compete with Apple iPad, they had to provide really fast solution. This is how MS come up with WinRT =)

Let’s have a quick look over the architecture. Oh, don’t be fooled by metro style apps being three times bigger than desktop apps. Microsoft tries very hard to emphasis Metro style Apps, though, as I said, they are mostly meant for tablets, so still most of the LOB applications will be delivered as a standard smart client desktop apps.

WinRT

From the slide above we clearly see that WinRT is only about Metro style apps. There is also one more thing. For regular Desktop Apps, nothing really changes. So let’s put an end to statements saying that WPF and Silverlight are dead. They’re not! Silverlight 5 is coming out soon, with the RC version already available, and we have WPF 4.5 preview available as well, with cool new feature coming (be sure to check Jonathan Antoine’s blog).

So as I said, it turned out that Silverlight and WPF are still there, they’re all good, and they are XAML based. As you see, the brand new WinRT is also XAML based, so another way of looking at it is that it is yet another manifestation of XAML framework. We have WPF, we have Silverlight, and now we have WinRT.

WinRT has been entirely written in native C++. The did that because they wanted performance, and give the opportunity to C++ devs to build vNext apps for Windows. Besides, Microsoft is constantly saying that the performance is the top priority for them =) What is super important is that WinRT components can be consumed from a variety of languages, including C++, C#, VB, and JavaScript. To enable this scenario, MS baked in a support for metadata, which describes the WinRT objects. As a side note, the metadata uses standard CLR metadata format (ECMA 335), so it can be ILDASMed for instance ;) So each native WinRT component is packed up in a standard native dll, and is accompanied with *.winmd file, which carries all the metadata information for that dll. In the Win8 Developer Preview, all metadata files for standard WinRT dlls are located on C:\Windows\System32\WinMetadata.

As far as the Graphics & Media part is concerned, the framework is a bit similar to Silverligh. UIElement, FrameworkElement, it’s all there. What is different though is the default namespace. We were used to System.Windows.*, now everything is in Windows.*, with the UI part being located in Windows.UI.XAML.* namespace.

That is pretty much it when it comes to WinRT overview, at the end of this post are link to good articles discussing WinRT in more depth.

Why bother writing WinRT components

It comes without surprise that MS allows us to create custom WinRT objects. What’s great about it, is that we can leverage the same metadata system MS is using in the “standard” WinRT, which means we can code our WinRT object in any supported languages (currently C++, C#, VB) and consume it from other languages without any effort. This means we can write performance critical code in C++ and consume it in C#, without resorting to COM interop or P/Invoke. Awesome!

So answering the question, building WinRT components allows developers to share their codebase with other projects, written in any language, targeting any platform. Say you want to develop custom control. If you develop it in C# as a regular CLR dll, you will be able to consume it only in C#. But if you make it a WinRT object, you can leverage it from any Metro app. Okay, so why bother writing such component in C++ then? Because of performance! Do you know why Silverlight is faster than WPF ? Because it’s C++ under the hoods! Don’t believe ? Try reflecting Grid in SL ;)

Summing up this short paragraph, you definitely want to write WinRT component if you want to share you code with other WinRT objects, running on different platforms. And you want to do it in C++ because of the performance.

Writing custom panel in native C++

So finally, we reached to the most interesting part, which is the code. So here I will demonstrate how to develop custom, native WinRT object. To do so, I will use custom panel as an example. Let’s say we want to develop CircularPanel, which layouts all its children in a circle. Let’s begin, shall we ?

Go to Visual Studio 2011 For Windows 8 Developer Preview, and add new C# empty project. Here we will consume our C++ WinRT object. Now go again and add new project, this time use C++ and WinRT Component template. Delete the WinRTComponent1 source and header files, and add CircularPanel.cpp and CircularPanel.h files. You should have something similar in Solution Explorer.

image

Time to fill in some code! Open up CircularPanel.h file and paste in this code.

#pragma once

using namespace Windows::Foundation;

namespace WinRT
{
namespace Controls
{
public ref class CircularPanel sealed : public Windows::UI::Xaml::Controls::Panel
{
private:
float _maxChildHeight;
float _maxChildWidth;

public:
CircularPanel ();
~CircularPanel ();

protected:
virtual Size ArrangeOverride(Size finalSize) override;

virtual Size MeasureOverride(Size availableSize) override;
};
}
}


As you can see no rocket science there. We define our CircularPanel class as ref so that it will act as a reference type, and we derive it from Windows.UI.Xaml.Controls.Panel class. Please note we declare MeasureOverride and ArrangeOverride methods that we will override to provide panel’s functionality. Now let’s go to the pane source.



// CircularPanel.cpp

#include "pch.h"
#include "CircularPanel.h"
#include "math.h"

#define PI 3.1415926

using namespace WinRT::Controls;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;

float Max(float x, float y)
{
if (x > y) return x;

return y;
}

float Min(float x, float y)
{
if (x > y) return y;

return x;
}

CircularPanel::CircularPanel ()
{
}

CircularPanel::~CircularPanel ()
{
}

Size CircularPanel::ArrangeOverride(Size finalSize)
{
Point centerPoint = Point();
centerPoint.X = finalSize.Width / 2;
centerPoint.Y = finalSize.Height / 2;

float radius = Min(centerPoint.X, centerPoint.Y) - (_maxChildWidth + _maxChildHeight) / 4;
float degreesPerChild = 360.0 / Children->Size;
float currentDegree = 0;

for (int i = 0; i < Children->Size; i++)
{
UIElement^ element = Children->GetAt(i);
float x = centerPoint.X + radius * cos(currentDegree * PI / 180);
float y = centerPoint.Y + radius * sin(currentDegree * PI / 180);

Rect rect = Rect();
rect.X = x - element->DesiredSize.Width / 2;
rect.Y = y - element->DesiredSize.Height / 2;
rect.Width = element->DesiredSize.Width;
rect.Height = element->DesiredSize.Height;

// Arrange child element
element->Arrange(rect);

currentDegree += degreesPerChild;
}

return finalSize;
}

Size CircularPanel::MeasureOverride(Size availableSize)
{
for (int i = 0; i < Children->Size; i++)
{
UIElement^ element = Children->GetAt(i);
element->Measure(availableSize);

_maxChildWidth = Max(_maxChildWidth, element->DesiredSize.Width);
_maxChildHeight = Max(_maxChildHeight, element->DesiredSize.Height);
}

return availableSize;
}

There is a little bit of math involved, but rather easy stuff. I don't want to discuss the code itself, just note the syntax. For example, we reference reference types with ^ (like UIElement^). Children collection doesn’t have Count property but Size field instead. Yeah, we’re in C++ world!

Now its time to tell our C# app about our native WinRT project. And this is were troubles start ;) Normally, we would go to Add Reference dialog and add it from Solution projects (no need to manually pick up the assembly), but this feature seems not working right now, although some say it is possible to add C++ WinRT project from Add Reference dialog, just clean the native project before. Didn’t work for me, but who knows, maybe will work for you! So instead go to Add Reference dialog and hit the Browse button. Navigate to the root folder of your solution, go to Debug and select the YourNativeProjectName.winmd file. This is quite interesting step – we reference a native dll using its Windows metadata file. So when you go ahead and compile the solution, you will see… a build error! At least on x64 bit machine.



image



I’m not sure if you get exactly the same error, although you should. So the problem is that our native component is targeting Win32 platform, which is x86, but we reference it from Any CPU C# project. This won’t work. Go to Configuration Manager and change C# project to target x86.



image



So now if you go ahead and compile, you will get another error.



image



This one is also easy to fix. So our component uses Platform.IDisposable, which needs to be referenced. So go back to Add Reference dialog and add Microsoft.VCLib dependency. Now the compilation should succeed.



So since the compilation succeeded, let’s actually make use of our WinRT object. But there is yet another gotcha! For unknown reason, I couldn’t get this panel to work in XAML. I got compile error saying this component is unknown. Referencing it from code worked, however.



partial class MainPage
{
public MainPage()
{
InitializeComponent();

Loaded += new RoutedEventHandler(MainPage_Loaded);
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
CircularPanel panel = new CircularPanel();

panel.Children.Add(new Button() { Content = "1", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "2", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "3", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "4", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "5", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "6", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "7", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "8", Width = 100, Height = 100 });
panel.Children.Add(new Button() { Content = "9", Width = 100, Height = 100 });

LayoutRoot.Children.Add(panel);
}
}

When you run the app, you should see a couple of buttons arranged in a circle :)



Conclusion



Microsoft did pretty good job with latest Win8. The new metro style apps, though meant for tablets, are truly great. When it comes to development, the Developer Preview is still pretty rough on the edges, but its about a good time to start playing with it, and upgrading skills. When it comes to WinRT development, it feels like WPF/SL.The base concepts like dependency properties, routed events, XAML, it’s all the same! This is a big strength of this platform. Well done MS!



Posts worth reading



A bad picture is worth a thousand long discussions


My thoughts about Build, Windows 8, WinRT, XAML and Silverlight


Welcome to Zombieland, the Metro Style Land of WinRT and the Undead

Win8, Metro Apps and Silverlight


2 comments:

MysteryDev said...

Thanks a massive bunch for the clear summary about the road ahead. It really helped clear the confusion for me.

Anonymous said...

Hi,
This is good post and i have small doubt in it.. Is it supported for Scroll. Since IscrollInfo is not avail in WinRT How it is sync with Scroll Viewer.