How do I do an interlocked exchange of a hat pointer?

Raymond Chen

If you work with C++/CX, then you spend a lot of time working with types that wear hats. These are basically super-smart pointers. In addition to performing automatic Add­Ref and Release, they also perform automatic Query­Interface if you call a method that belongs to an interface other than the default one.

How do you perform an atomic exchange of these things?

The trick is realizing that a hat pointer is the same size as a raw pointer because it’s physically represented as a pointer to the default interface of the underlying type. Therefore, you can perform the interlocked operation on the raw pointer, provided of course that the thing you are exchanging in can legally be placed in such a pointer.

Even if a hat pointer weren’t the size of a raw pointer, what’s important are that (1) it’s the size of something that can be atomically exchanged, and (2) it is self-contained, without dependencies on other memory.

template<typename T, typename U>
T^ InterlockedExchangeRefPointer(T^* target, U value)
{
  static_assert(sizeof(T^) == sizeof(void*),
    "InterlockedExchangePointer is the wrong size");
  T^ exchange = value;
  void** rawExchange = reinterpret_cast<void**>(&exchange);
  void** rawTarget = reinterpret_cast<void**>(target);
  *rawExchange = static_cast<IInspectable*>(
    InterlockedExchangePointer(rawTarget, *rawExchange));
  return exchange;
}

Okay, what’s going on here?

First, we verify our assumption: Namely, that a hat pointer is the same size as a raw pointer, because we’re about to exchange the contents of the two things, and we need to be sure that we’re exchanging the correct number of bytes.

Next, we convert the value to a compatible pointer. This allows you to pass anything convertible to T^ as the second parameter, rather than having to pass something that is exactly a T^. If the function had bee prototyped as

template<typename T>
T^ InterlockedExchangeRefPointer(T^* target, T^ value)

then you would have gotten type inference errors if you pass a second parameter that is not literally a T^, but which can be converted to one (for example, nullptr) because the compiler can’t figure out what T should be.

Once we’ve converted the value into a T^ we can proceed with the raw exchange of contents. The raw­Exchange variable points to the variable exchange, but viewing it as a raw pointer rather than a hat pointer. Similarly, the raw­Target variable points to the target as a raw pointer.

We then ask Interlocked­Exchange­Pointer to do the dirty work of exchanging the values. We put the previous value of the target back into exchange (via the alias known as raw­Exchange).

Putting the answer back into exchange lets us return the smart version of the variable back to our caller.

So that’s it. This is really just a fancy way of writing

THING InterlockedExchangeThing(Thing* thing, Thing newThing)
{
 newThing = InterlockedExchangeSizeOfThing(thing, newThing);
 return newThing;
}