Connecting C++ and XAML

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 theSampleBindableClass 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 aSampleBindableClass^ ) 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 typeObject^ (the instance of the class to get from or set to) and either a return parameter of typeObject^ (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 Getterand 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 athttp://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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值