Move to C++/WinRT from C++/CX

This topic shows how to port the code in a C++/CX project to its equivalent in C++/WinRT.

Porting strategies

If you want to gradually port your C++/CX code to C++/WinRT, then you can. C++/CX and C++/WinRT code can coexist in the same project, with the exceptions of XAML compiler support and Windows Runtime components. For those two exceptions, you'll need to target either C++/CX or C++/WinRT within the same project.

Important

If your project builds a XAML application, then one workflow that we recommend is to first create a new project in Visual Studio using one of the C++/WinRT project templates (see Visual Studio support for C++/WinRT). Then, start copying source code and markup over from the C++/CX project. You can add new XAML pages with Project > Add New Item... > Visual C++ > Blank Page (C++/WinRT).

Alternatively, you can use a Windows Runtime component to factor code out of the XAML C++/CX project as you port it. Either move as much C++/CX code as you can into a component, and then change the XAML project to C++/WinRT. Or else leave the XAML project as C++/CX, create a new C++/WinRT component, and begin porting C++/CX code out of the XAML project and into the component. You could also have a C++/CX component project alongside a C++/WinRT component project within the same solution, reference both of them from your application project, and gradually port from one to the other. See Interop between C++/WinRT and C++/CX for more details on using the two language projections in the same project.

Note

Both C++/CX and the Windows SDK declare types in the root namespace Windows. A Windows type projected into C++/WinRT has the same fully-qualified name as the Windows type, but it's placed in the C++ winrt namespace. These distinct namespaces let you port from C++/CX to C++/WinRT at your own pace.

Bearing in mind the exceptions mentioned above, the first step in porting a C++/CX project to C++/WinRT is to manually add C++/WinRT support to it (see Visual Studio support for C++/WinRT). To do that, install the Microsoft.Windows.CppWinRT NuGet package into your project. Open the project in Visual Studio, click Project > Manage NuGet Packages... > Browse, type or paste Microsoft.Windows.CppWinRT in the search box, select the item in search results, and then click Install to install the package for that project. One effect of that change is that support for C++/CX is turned off in the project. It's a good idea to leave support turned off so that build messages help you find (and port) all of your dependencies on C++/CX, or you can turn support back on (in project properties, C/C++ > General > Consume Windows Runtime Extension > Yes (/ZW)), and port gradually.

Alternatively, manually add the following property to your .vcxproj file using the C++/WinRT project property page in Visual Studio. For a list of similar customization options (which fine-tune the behavior of the cppwinrt.exe tool), see the Microsoft.Windows.CppWinRT NuGet package readme.

<syntaxhighlight lang="xml">
  <PropertyGroup Label="Globals">
    <CppWinRTProjectLanguage>C++/CX</CppWinRTProjectLanguage>
  </PropertyGroup>
</syntaxhighlight>

Next, ensure that project property General > Target Platform Version is set to 10.0.17134.0 (Windows 10, version 1803) or greater.

In your precompiled header file (usually pch.h), include winrt/base.h.

#include <winrt/base.h>

If you include any C++/WinRT projected Windows API headers (for example, winrt/Windows.Foundation.h), then you don't need to explicitly include winrt/base.h like this because it will be included automatically for you.

If your project is also using Windows Runtime C++ Template Library (WRL) types, then see Move to C++/WinRT from WRL.

File-naming conventions

XAML markup files

C++/CX C++/WinRT
Developer XAML files MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (see below)
Generated XAML files MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Notice that C++/WinRT removes the .xaml from the *.h and *.cpp file names.

C++/WinRT adds an additional developer file, the Midl file (.idl). C++/CX autogenerates this file internally, adding to it every public and protected member. In C++/WinRT, you add and author the file yourself. For more details, code examples, and a walkthrough of authoring IDL, see XAML controls; bind to a C++/WinRT property.

Also see Factoring runtime classes into Midl files (.idl)

Runtime classes

C++/CX doesn't impose restrictions on the names of your header files; it's common to put multiple runtime class definitions into a single header file, especially for small classes. But C++/WinRT requires that each runtime class has its own header file named after the class name.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

Less common (but still legal) in C++/CX is to use differently-named header files for XAML custom controls. You'll need to rename these header file to match the class name.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Header file requirements

C++/CX doesn't require you to include any special header files, because it internally autogenerates header files from .winmd files. It's common in C++/CX to use using directives for namespaces that you consume by name.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

The using namespace Windows::Media::Playback directive lets us write MediaPlaybackItem without a namespace prefix. We also touched the Windows.Media.Core namespace, because item->VideoTracks->GetAt(0) returns a Windows.Media.Core.VideoTrack. But we didn't have to type the name VideoTrack anywhere, so we didn't need a using Windows.Media.Core directive.

But C++/WinRT requires you to include a header file corresponding to each namespace that you consume, even if you don't name it.

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

On the other hand, even though the MediaPlaybackItem.AudioTracksChanged event is of type TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs>, we don't need to include winrt/Windows.Foundation.Collections.h because we didn't use that event.

C++/WinRT also requires you to include header files for namespaces that are consumed by XAML markup.

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

Using the Rectangle class means that you have to add this include.

// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>

If you forget a header file, then everything will compile okay but you'll get linker errors because the consume_ classes are missing.

Parameter-passing

When writing C++/CX source code, you pass C++/CX types as function parameters as hat (^) references.

void LogPresenceRecord(PresenceRecord^ record);

In C++/WinRT, for synchronous functions, you should use const& parameters by default. That will avoid copies and interlocked overhead. But your coroutines should use pass-by-value to ensure that they capture by value and avoid lifetime issues (for more details, see Concurrency and asynchronous operations with C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

A C++/WinRT object is fundamentally a value that holds an interface pointer to the backing Windows Runtime object. When you copy a C++/WinRT object, the compiler copies the encapsulated interface pointer, incrementing its reference count. Eventual destruction of the copy involves decrementing the reference count. So, only incur the overhead of a copy when necessary.

Variable and field references

When writing C++/CX source code, you use hat (^) variables to reference Windows Runtime objects, and the arrow (->) operator to dereference a hat variable.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

When porting to the equivalent C++/WinRT code, you can get a long way by removing the hats, and changing the arrow operator (->) to the dot operator (.). C++/WinRT projected types are values, and not pointers.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

The default constructor for a C++/CX hat reference initializes it to null. Here's a C++/CX code example in which we create a variable/field of the correct type, but one that's uninitialized. In other words, it doesn't initially refer to a TextBlock; we intend to assign a reference later.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

For the equivalent in C++/WinRT, see Delayed initialization.

Properties

The C++/CX language extensions include the concept of properties. When writing C++/CX source code, you can access a property as if it were a field. Standard C++ does not have the concept of a property so, in C++/WinRT, you call get and set functions.

In the examples that follow, XboxUserId, UserState, PresenceDeviceRecords, and Size are all properties.

Retrieving a value from a property

Here's how you get a property value in C++/CX.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

The equivalent C++/WinRT source code calls a function with the same name as the property, but with no parameters.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Note that the PresenceDeviceRecords function returns a Windows Runtime object that itself has a Size function. As the returned object is also a C++/WinRT projected type, we dereference using the dot operator to call Size.

Setting a property to a new value

Setting a property to a new value follows a similar pattern. First, in C++/CX.

record->UserState = newValue;

To do the equivalent in C++/WinRT, you call a function with the same name as the property, and pass an argument.

record.UserState(newValue);

Creating an instance of a class

You work with a C++/CX object via a handle to it, commonly known as a hat (^) reference. You create a new object via the ref new keyword, which in turn calls RoActivateInstance to activate a new instance of the runtime class.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

A C++/WinRT object is a value; so you can allocate it on the stack, or as a field of an object. You never use ref new (nor new) to allocate a C++/WinRT object. Behind the scenes, RoActivateInstance is still being called.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

If a resource is expensive to initialize, then it's common to delay initialization of it until it's actually needed. As already mentioned, the default constructor for a C++/CX hat reference initializes it to null.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

The same code ported to C++/WinRT. Note the use of the std::nullptr_t constructor. For more info about that constructor, see Delayed initialization.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

How the default constructor affects collections

C++ collection types use the default constructor, which can result in unintended object construction.

Scenario C++/CX C++/WinRT (incorrect) C++/WinRT (correct)
Local variable, initially empty TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Member variable, initially empty class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Global variable, initially empty TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Vector of empty references std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Set a value in a map std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Array of empty references TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Pair std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

There's no shorthand for creating an array of empty references. You have to repeat nullptr for each element in the array. If you have too few, then the extras will be default-constructed.

More about the std::map example

The [] subscript operator for std::map behaves like this.

  • If the key is found in the map, return a reference to the existing value (which you can overwrite).
  • If the key isn't found in the map, then create a new entry in the map consisting of the key (moved, if movable) and a default-constructed value, and return a reference to the value (which you can then overwrite).

In other words, the [] operator always creates an entry in the map. This is different from C#, Java, and JavaScript.

Converting from a base runtime class to a derived one

It's common to have a reference-to-base that you know refers to an object of a derived type. In C++/CX, you use dynamic_cast to cast the reference-to-base into a reference-to-derived. The dynamic_cast is really just a hidden call to QueryInterface. Here's a typical example—you're handling a dependency property changed event, and you want to cast from DependencyObject back to the actual type that owns the dependency property.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

The equivalent C++/WinRT code replaces the dynamic_cast with a call to the IUnknown::try_as function, which encapsulates QueryInterface. You also have the option to call IUnknown::as, instead, which throws an exception if querying for the required interface (the default interface of the type you're requesting) is not returned. Here's a C++/WinRT code example.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

Derived classes

In order to derive from a runtime class, the base class must be composable. C++/CX doesn't require that you take any special steps to make your classes composable, but C++/WinRT does. You use the unsealed keyword to indicate that you want your class to be usable as a base class.

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

In your implementation header class, you must include the base class header file before you include the autogenerated header for the derived class. Otherwise you'll get errors such as "Illegal use of this type as an expression".

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

Event-handling with a delegate

Here's a typical example of handling an event in C++/CX, using a lambda function as a delegate in this case.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

This is the equivalent in C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Instead of a lambda function, you can choose to implement your delegate as a free function, or as a pointer-to-member-function. For more info, see Handle events by using delegates in C++/WinRT.

If you're porting from a C++/CX codebase where events and delegates are used internally (not across binaries), then winrt::delegate will help you to replicate that pattern in C++/WinRT. Also see Parameterized delegates, simple signals, and callbacks within a project.

Revoking a delegate

In C++/CX you use the -= operator to revoke a prior event registration.

myButton->Click -= token;

This is the equivalent in C++/WinRT.

myButton().Click(token);

For more info and options, see Revoke a registered delegate.

Boxing and unboxing

C++/CX automatically boxes scalars into objects. C++/WinRT requires you to call the winrt::box_value function explicitly. Both languages require you to unbox explicitly. See Boxing and unboxing with C++/WinRT.

In the tables that follows, we'll use these definitions.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Operation C++/CX C++/WinRT
Boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Unboxing i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX and C# raise exceptions if you try to unbox a null pointer to a value type. C++/WinRT considers this a programming error, and it crashes. In C++/WinRT, use the winrt::unbox_value_or function if you want to handle the case where the object is not of the type that you thought it was.

Scenario C++/CX C++/WinRT
Unbox a known integer i = (int)o; i = unbox_value<int>(o);
If o is null Platform::NullReferenceException Crash
If o is not a boxed int Platform::InvalidCastException Crash
Unbox int, use fallback if null; crash if anything else i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Unbox int if possible; use fallback for anything else auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Boxing and unboxing a string

A string is in some ways a value type, and in other ways a reference type. C++/CX and C++/WinRT treat strings differently.

The ABI type HSTRING is a pointer to a reference-counted string. But it doesn't derive from IInspectable, so it's not technically an object. Furthermore, a null HSTRING represents the empty string. Boxing of things not derived from IInspectable is done by wrapping them inside an IReference<T>, and the Windows Runtime provides a standard implementation in the form of the PropertyValue object (custom types are reported as PropertyType::OtherType).

C++/CX represents a Windows Runtime string as a reference type; while C++/WinRT projects a string as a value type. This means that a boxed null string can have different representations depending how you got there.

Furthermore, C++/CX allows you to dereference a null String^, in which case it behaves like the string "".

Operation C++/CX C++/WinRT
String type category Reference type Value type
null HSTRING projects as (String^)nullptr hstring{}
Are null and "" identical? Yes Yes
Validity of null s = nullptr;
s->Length == 0 (valid)
s = nullptr;
s.size() == 0 (valid)
Box a string o = s; o = box_value(s);
If s is null o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
If s is "" o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr;
Box a string preserving null o = s; o = s.empty() ? nullptr : box_value(s);
Force-box a string o = PropertyValue::CreateString(s); o = box_value(s);
Unbox a known string s = (String^)o; s = unbox_value<hstring>(o);
If o is null s == nullptr; // equivalent to "" Crash
If o is not a boxed string Platform::InvalidCastException Crash
Unbox string, use fallback if null; crash if anything else s = o ? (String^)o : fallback; s = o ? unbox_value<hstring>(o) : fallback;
Unbox string if possible; use fallback for anything else auto box = dynamic_cast<IBox<String^>^>(o);
s = box ? box->Value : fallback;
s = unbox_value_or<hstring>(o, fallback);

In the two unbox with fallback cases above, it's possible that a null string was force-boxed, in which case the fallback won't be used. The resulting value will be an empty string because that is what was in the box.

Concurrency and asynchronous operations

The Parallel Patterns Library (PPL) (concurrency::task, for example) was updated to support C++/CX hat references.

For C++/WinRT, you should use coroutines and co_await instead. For more info, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Consuming objects from XAML markup

In a C++/CX project, you can consume private members and named elements from XAML markup. But in C++/WinRT, all entities consumed by using the XAML {x:Bind} markup extension must be exposed publicly in IDL.

Also, binding to a Boolean displays true or false in C++/CX, but it shows Windows.Foundation.IReference`1<Boolean> in C++/WinRT.

For more info, and code examples, see Consuming objects from markup.

Mapping C++/CX Platform types to C++/WinRT types

C++/CX provides several data types in the Platform namespace. These types are not standard C++, so you can only use them when you enable Windows Runtime language extensions (Visual Studio project property C/C++ > General > Consume Windows Runtime Extension > Yes (/ZW)). The table below helps you port from Platform types to their equivalents in C++/WinRT. Once you've done that, since C++/WinRT is standard C++, you can turn off the /ZW option.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ See Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Port Platform::Agile^ to winrt::agile_ref

The Platform::Agile^ type in C++/CX represents a Windows Runtime class that can be accessed from any thread. The C++/WinRT equivalent is winrt::agile_ref.

In C++/CX.

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

In C++/WinRT.

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

Port Platform::Array^

Your options include using an initializer list, a std::array, or a std::vector. For more info, and code examples, see Standard initializer lists and Standard arrays and vectors.

Port Platform::Exception^ to winrt::hresult_error

The Platform::Exception^ type is produced in C++/CX when a Windows Runtime API returns a non S_OK HRESULT. The C++/WinRT equivalent is winrt::hresult_error.

To port to C++/WinRT, change all code that uses Platform::Exception^ to use winrt::hresult_error.

In C++/CX.

catch (Platform::Exception^ ex)

In C++/WinRT.

catch (winrt::hresult_error const& ex)

C++/WinRT provides these exception classes.

Exception type Base class HRESULT
winrt::hresult_error call hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Note that each class (via the hresult_error base class) provides a to_abi function, which returns the HRESULT of the error, and a message function, which returns the string representation of that HRESULT.

Here's an example of throwing an exception in C++/CX.

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

And the equivalent in C++/WinRT.

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Port Platform::Object^ to winrt::Windows::Foundation::IInspectable

Like all C++/WinRT types, winrt::Windows::Foundation::IInspectable is a value type. Here's how you initialize a variable of that type to null.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Port Platform::String^ to winrt::hstring

Platform::String^ is equivalent to the Windows Runtime HSTRING ABI type. For C++/WinRT, the equivalent is winrt::hstring. But with C++/WinRT, you can call Windows Runtime APIs using C++ Standard Library wide string types such as std::wstring, and/or wide string literals. For more details, and code examples, see String handling in C++/WinRT.

With C++/CX, you can access the Platform::String::Data property to retrieve the string as a C-style const wchar_t* array (for example, to pass it to std::wcout).

auto var{ titleRecord->TitleName->Data() };

To do the same with C++/WinRT, you can use the hstring::c_str function to get a null-terminated C-style string version, just as you can from std::wstring.

auto var{ titleRecord.TitleName().c_str() };

When it comes to implementing APIs that take or return strings, you typically change any C++/CX code that uses Platform::String^ to use winrt::hstring instead.

Here's an example of a C++/CX API that takes a string.

void LogWrapLine(Platform::String^ str);

For C++/WinRT you could declare that API in MIDL 3.0 like this.

// LogType.idl
void LogWrapLine(String str);

The C++/WinRT toolchain will then generate source code for you that looks like this.

void LogWrapLine(winrt::hstring const& str);

ToString()

C++/CX types provide the Object::ToString method.

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

C++/WinRT doesn't directly provide this facility, but you can turn to alternatives.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT also supports winrt::to_hstring for a limited number of types. You'll need to add overloads for any additional types you want to stringify.

Language Stringify int Stringify enum
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

In the case of stringifying an enum, you will need to provide the implementation of winrt::to_hstring.

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

These stringifications are often consumed implicitly by data binding.

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

These bindings will perform winrt::to_hstring of the bound property. In the case of the second example (the StatusEnum), you must provide your own overload of winrt::to_hstring, otherwise you'll get a compiler error.

String-building

C++/CX and C++/WinRT defer to the standard std::wstringstream for string building.

C++/CX C++/WinRT
Append string, preserving nulls stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Append string, stop on first null stream << s->Data(); stream << s.c_str();
Extract result ws = stream.str(); ws = stream.str();

More examples

In the examples below, ws is a variable of type std::wstring. Also, while C++/CX can construct a Platform::String from an 8-bit string, C++/WinRT doesn't do that.

Operation C++/CX C++/WinRT
Construct string from literal String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Convert from std::wstring, preserving nulls String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Convert from std::wstring, stop on first null String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Convert to std::wstring, preserving nulls std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Convert to std::wstring, stop on first null std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Pass literal to method Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Pass std::wstring to method Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

Important APIs