December 30th, 2021

The C++/CX String^ is not an object, even though it wears a hat

C++/CX refers to Windows Runtime strings as Platform::String^ with a hat instead of a star. But even though the string wears a hat, it is not an Object^.

The String^ type is a representation of the Windows Runtime HSTRING. And of the rules of HSTRING is that a null pointer is a valid HSTRING, and it represents the empty string, that is, a string with no characters.

This makes String^ a strange sort of beast.

String^ s = L""; // sets s = nullptr

If you assign an empty string to it, you get nullptr back.

void f(String ^s)
{
    if (s) { /* string is not empty */ }
}

Testing a String^ against nullptr tests whether the string is empty.

String ^s = nullptr; // represents empty string
auto data = s->Data(); // legal! returns pointer to L""
auto length = s->Length(); // legal! returns 0.
auto equal = s->Equals(L"nope"); // legal! returns false.

That’s right: I dereferenced a null pointer and it felt good.

Calling methods on a null String^ pointer is legal, and the operations are performed on an empty string.

This weird behavior of null String^ pointers has consequences beyond just strings. If you convert a null String^ to Object^ (a boxing operation), the null-ness is preserved:

String^ s = L""; // s is nullptr
Object^ o = s; // o is nullptr!

This differs from the behavior in other projections like C#, JavaScript, and C++/WinRT, where boxing an empty string produces a non-null object (that in turn holds an empty string).

The fact that a String^ is not an Object^ means that you cannot reinterpret between them.

String^ s = /* some value */;
Object^ o = reinterpret_cast<Object^>(s); // crash

The reinterpret_cast will treat a String^ as an Object^. But a String^ is secretly a HSTRING, whereas an Object^ is secretly an IInspectable*. The reinterpret-cast tells the compiler to treat this HSTRING as if it were an IInspectable*, and bad things happen, since the compiler is going to try to call the AddRef method from the IInspectable‘s vtable, but HSTRINGs don’t have a vtable, much less a vtable with AddRef in slot 1.

What you need to do is box the string into an object and unbox the object back into a string.

Object^o = s; // box the string into an object
String^s = static_cast<String^>(o); // unbox the object into a string

Bonus chatter: C++/CX delegates are also not objects, even though they too wear hats.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Alexis Ryan

    C++ compilers oddly tend to be ok calling non virtual methods on null objects even if that's headed into undefined behaviour territory. Better hope the method checks this for null.Sounds like the projection is probably doing tnis with the methods for String

    Imo empty string should be different to null strings as a null thing is not a thing while an empty string is still a string. Most of the time this tends to be the...

    Read more
    • MGetz

      So this is an interesting one, C++11 and beyond forbid exactly that calling an instance method on a null pointer is explicitly undefined behavior. Unfortunately some code bases rely on that behavior. I can't recall the specific code bases but I know that the Linux Kernel has run into a version of this as well to the point they now compile with explicitly. I know some of the libraries used by Chromium (and...

      Read more
    • Raymond ChenMicrosoft employee Author · Edited

      In some languages (like C++), strings are value types. There is no “null” value for std::string. Even in languages where strings are reference types, distinguishing an empty string from a null string can be cumbersome (such as in JavaScript and Perl), and coding conventions generally consider empty and null strings as equivalent.

      • Nick · Edited

        Even in a language like C# tries to skirt the line between reference and value. It has value equality and assignment semantics, immutability, compile-time const-ness and interning... but is always showing it's true reference self whenever null shows up.

        I've often wished the C# language designers had gone all-in and made string a value object alongside int. Maybe it would be clunky, but using a special value to represent a missing string...

        Read more
  • David Wolff

    > That’s right: I dereferenced a null pointer and it felt good.

    Oh bravo!