Hi, I’m Andy Rich, a tester on the C++ frontend compiler and one of the primary testers of the C++/CX language extensions. If you’re like me, making use of a technology without understanding how it works can be confusing and frustrating. This blog post will help explain how XAML and C++ work together in the build system to make a Windows Store application that still respects the C++ language build model and syntax. (Note: this blog post is targeted towards Windows Store app developers.)
The Build Process
From a user-facing standpoint, Pages and other custom controls are really a trio of user-editable files. For example, the definition of the class MainPage is comprised of three files: MainPage.xaml, MainPage.xaml.h, and MainPage.xaml.cpp. Both mainpage.xaml and mainpage.xaml.h contribute to the actual definition of the MainPage class, while MainPage.xaml.cpp provides the method implementations for those methods defined in MainPage.xaml.h. However, how this actually works in practice is far more complex.
This drawing is very complex, so please bear with me while I break it down into its constituent pieces.
Every box in the diagram represents a file. The light-blue files on the left side of the diagram are the files which the user edits. These are the only files that typically show up in the Solution Explorer. I’ll speak specifically about MainPage.xaml and its associated files, but this same process occurs for all xaml/h/cpp trios in the project.
The first step in the build is XAML compilation, which will actually occur in several steps. First, the user-edited MainPage.xaml file is processed to generate MainPage.g.h. This file is special in that it is processed at design-time (that is, you do not need to invoke a build in order to have this file be updated). The reason for this is that edits you make to MainPage.xaml can change the contents of the MainPage class, and you want those changes to be reflected in your Intellisense without requiring a rebuild. Except for this step, all of the other steps only occur when a user invokes a Build.
Partial Classes
You may note that the build process introduces a problem: the class MainPage actually has two definitions, one that comes from MainPage.g.h:
partial ref class MainPage : public ::Windows::UI::Xaml::Controls::Page,
public ::Windows::UI::Xaml::Markup::IComponentConnector
{
public:
void InitializeComponent();
virtual void Connect(int connectionId, ::Platform::Object^ target);
private:
bool _contentLoaded;
};
And one that comes from MainPage.xaml.h:
public ref class MainPage sealed
{
public:
MainPage();
protected:
virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
};
This issue is reconciled via a new language extension: Partial Classes.
The compiler parsing of partial classes is actually fairly straightforward. First, all partial definitions for a class must be within one translation unit. Second, all class definitions must be marked with the keyword partial except for the very last definition (sometimes referred to as the ‘final’ definition). During parsing, the partial definitions are deferred by the compiler until the final definition is seen, at which point all of the partial definitions (along with the final definition) are combined together and parsed as one definition. This feature is what enables both the XAML-compiler-generated file MainPage.g.h and the user-editable file MainPage.xaml.h to contribute to the definition of the MainPage class.
Compilation
For compilation, MainPage.g.h is included in MainPage.xaml.h, which is further included in MainPage.xaml.cpp. These files are compiled by the C++ compiler to produce MainPage.obj. (This compilation is represented by the red lines in the above diagram.) MainPage.obj, along with the other obj files that are available at this stage are passed through the linker with the switch /WINMD:ONLY to generate the Windows Metadata (WinMD) file for the project. This process is denoted in the diagram by the orange line. At this stage we are not linking the final executable, only producing the WinMD file, because MainPage.obj still contains some unresolved externals for the MainPage class, namely any functions which are defined in MainPage.g.h (typically the InitializeComponent and Connect functions). These definitions were generated by the XAML compiler and placed into MainPage.g.hpp, which will be compiled at a later stage.
MainPage.g.hpp, along with the *.g.hpp files for the other XAML files in the project, will be included in a file called XamlTypeInfo.g.cpp. This is for build performance optimization: these various .hpp files do not need to be compiled separately but can be built as one translation unit along with XamlTypeInfo.g.cpp, reducing the number of compiler invocations required to build the project.
Data Binding and XamlTypeInfo
Data binding is a key feature of XAML architecture, and enables advanced design patterns such as MVVM. C++ fully supports data binding; however, in order for the XAML architecture to perform data binding, it needs to be able to take the string representation of a field (such as “FullName”) and turn that into a property getter call against an object. In the managed world, this can be accomplished with reflection, but native C++ does not have a built-in reflection model.
Instead, the XAML compiler (which is itself a .NET application) loads the WinMD file for the project, reflects upon it, and generates C++ source that ends up in the XamlTypeInfo.g.cpp file. It will generate the necessary data binding source for any public class marked with the Bindable attribute.
It may be instructive to look at the definition of a data-bindable class and see what source is generated that enables the data binding to succeed. Here is a simple bindable class definition:
[Windows::UI::Xaml::Data::Bindable]
public ref class SampleBindableClass sealed {
public:
property Platform::String^ FullName;
};
When this is compiled, as the class definition is public, it will end up in the WinMD file as seen here:
This WinMD is processed by the XAML compiler and adds source to two important functions within XamlTypeInfo.g.cpp: CreateXamlType and CreateXamlMember.
The source added to CreateXamlType generates basic type information for the SampleBindableClass type, provides an Activator (a function that can create an instance of the class) and enumerates the members of the class:
if (typeName == L"BlogDemoApp.SampleBindableClass")
{
XamlUserType^ userType = ref new XamlUserType(this, typeName, GetXamlTypeByName(L"Object"));
userType->KindOfType = ::Windows::UI::Xaml::Interop::TypeKind::Custom;
userType->Activator =
[]() -> Platform::Object^
{
return ref new ::BlogDemoApp::SampleBindableClass();
};
userType->AddMemberName(L"FullName");
userType->SetIsBindable();
return userType;
}
Note how a lambda is used to adapt the call to ref new (which will return a SampleBindableClass^) into the Activator function (which always returns an Object^).
From String to Function Call
As I mentioned previously, the fundamental issue with data binding is transforming the text name of a property (in our example, “FullName”) into the getter and setter function calls for this property. This translation magic is implemented by the XamlMember class.
XamlMember stores two function pointers: Getter and Setter. These function pointers are defined against the base type Object^ (which all WinRT and fundamental types can convert to/from). A XamlUserType stores a map<String^, XamlUserType^>; when data binding requires a getter or setter to be called, the appropriate XamlUserType can be found in the map and its associated Getter or Setter function pointer can be invoked.
The source added to CreateXamlMember initializes these Getter and Setter function pointers for each property. These function pointers always have a parameter of type Object^ (the instance of the class to get from or set to) and either a return parameter of type Object^ (in the case of a getter) or have a second parameter of type Object^ (for setters).
if (longMemberName == L"BlogDemoApp.SampleBindableClass.FullName")
{
XamlMember^ xamlMember = ref new XamlMember(this, L"FullName", L"String");
xamlMember->Getter =
[](Object^ instance) -> Object^
{
auto that = (::BlogDemoApp::SampleBindableClass^)instance;
return that->FullName;
};
xamlMember->Setter =
[](Object^ instance, Object^ value) -> void
{
auto that = (::BlogDemoApp::SampleBindableClass^)instance;
that->FullName = (::Platform::String^)value;
};
return xamlMember;
}
The two lambdas defined use the lambda ‘decay to pointer’ functionality to bind to Getter and Setter methods. These function pointers can then be called by the data binding infrastructure, passing in an object instance, in order to set or get a property based on only its name. Within the lambdas, the generated code adds the proper type casts in order to marshal to/from the actual types.
Final Linking and Final Thoughts
After compiling the xamltypeinfo.g.cpp file into xamltypeinfo.g.obj, we can then link this object file along with the other object files to generate the final executable for the program. This executable, along with the winmd file previously generated, and your xaml files, are packaged up into the app package that makes up your Windows Store Application.
A note: the Bindable attribute described in this post is one way to enable data binding in WinRT, but it is not the only way. Data binding can also be enabled on a class by implementing either the ICustomPropertyProvider interface or IMap<String^,Object^>. These other implementations would be useful if the Bindable attribute cannot be used, particularly if you want a non-public class to be data-bindable.
For additional info, I recommend looking at this walkthrough, which will guide you through building a fully-featured Windows Store Application in C++/XAML from the ground up. The Microsoft Patterns and Practices team has also developed a large application which demonstrates some best practices when developing Windows Store Applications in C++: project Hilo. The sources and documentation for this project can be found at http://hilo.codeplex.com/. (Note that this project may not yet be updated for the RTM release of Visual Studio.)
I hope this post has given you some insight into how user-edited files and generated files are compiled together to produce a functional (and valid) C++ program, and given you some insight into how the XAML data binding infrastructure actually works behind the scenes.
0 comments