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

Raymond

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.

5 comments

Comments are closed. Login to edit/delete your existing comments

  • David Wolff

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

    Oh bravo!

  • 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 case so seems a bit odd for HSTRING to go against expected conventions

    • Raymond ChenMicrosoft employee

      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

        Even in a language like C# string 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 string.Null value to represent a missing string would probably be a decent tradeoff in both readability (such as foo.IsNullOrEmpty()) as well as reducing a lot of null reference exceptions.

        Maybe the price of all the extra boxing (especially pre-generics) was a deterrent.

    • MGetz

      So this is an interesting one, C++11 and beyond forbid exactly that calling an instance method on a null this 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 -fno-delete-null-pointer-checks explicitly. I know some of the libraries used by Chromium (and thus Edge and Chrome) have relied on this behavior in the past too. But there has been a concerted effort to clean that up to prevent potential issues.