As I noted some time ago, the empty Windows Runtime string is represented by a null pointer. This has natural but perhaps surprising consequences: Even though it is a null pointer, the empty Windows Runtime string is a real string, with hopes and dreams. Or at least a length and data.
At the ABI level, WindowsGetStringLen
reports that a null pointer string has a length of zero, and WindowsGetStringRawBuffer
gives you a buffer that consists of a single null terminator.
Since an empty string and a null pointer are indistinguishable at the ABI layer, if you operate at the ABI layer (using raw HSTRING
s) or at a thin projection layer (such as C++/CX and C++/WinRT), you can take advantage of this equivalence.
For starters, you don’t need to check for a null pointer before trying to use the string, because a null pointer is a perfectly valid HSTRING
.
ABI | if (s != nullptr && |
if (s != nullptr && |
C++/CX | if (s != nullptr && |
if (s != nullptr && |
C++/WinRT | if (s != hstring{} && |
if (s != hstring{} && |
If you are checking for a nonempty string, you can just check for null. C++/WinRT and C++/CX even have special methods that tell you directly.
Slower way | Quicker way | |
---|---|---|
ABI | if (WindowsGetStringLen(s) != 0) |
if (s != nullptr) |
C++/CX | if (s->Length() != 0) |
if (!s->IsEmpty()) |
C++/WinRT | if (s.size() != 0) |
if (!s.empty()) |
Related: The C++/CX String^
is not an object, even though it wears a hat.
I’m pretty sure that the C++/CX syntax `s->Length()` is undefined behavior. C++ explicitly disallows this == nullptr, and compilers like Clang will optimize such paths as “unreachable”. Admittedly, Clang doesn’t support C++/CX, but similar C++-only wrappers could exist on Clang and syntax like the above could lead to confusing optimizations.
With that said, the WindowsGetStringLength and s.size() checks look good.
See the linked article.
s
is not a pointer. It’s a custom type that looks like a pointer (String^
).Actually, you can realistically create your own smart pointer type that semantically equates
nullptr
to""
, whose definitions ofoperator*
,operator[]
,operator->
etc handlenullptr
specially by returning a pointer/reference to a literal""
, whoseoperator==
equatesnullptr
to""
, etc.C++/CX will most likely be using a userdefined type, so ‘s’ won’t be nullptr … it will be a value of the userdefined type containing a field with the nullptr and defining the methods “properly” by calling the ABI. This needs the arrow operator to return another userdefined type and not “just” dereference the pointer, as the pointer points to nothing that has a length method. So given how you typically implement API shims I don’t see how this would invoke undefined behavior.
> If you are checking for a nonempty string, you can just check for null.
What if someone passes in an allocated string buffer that happens to point to an empty string (ie. just null terminator).
If you just check the pointer value – won’t you miss this scenario?
Checking further.. I guess this is why it is safe:
> One of the special rules for HSTRING is similar to the corresponding rule for BSTR, namely that a null pointer is equivalent to a zero-length string. But HSTRING takes it further: Not only is a null pointer equivalent to a zero-length string, but in fact a null pointer is the representation of a zero-length string. In other words, if you call WindowsCreateString and specify that the string has length zero, then out will come a null pointer.
You can’t get a HSTRING that is non-null for the case above – WindowsCreateString would have returned nullptr.
But this still looks dangerous. In C++ calls to a null object are UB.
This is one of those “it depends” situations. For example, the winrt::hstring class is a container, much like std::string.
So while the C API/ABI will use null to represent an empty HSTRING, I would be very surprised if any of the C++ projections would use null to indicate an empty HSTRING. But I only really know C++/WinRT, and I know that this projection uses references to pass parameters.