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


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;
}
Comments (12)
  1. Medinoc says:

    Is this how System::Threading::Interlocked::Exchange(T%, T) (from .Net 3.5 onwards) works?

    1. Medinoc says:

      Whoops, I misread the first paragraph, my bad.

      1. Ben Voigt (Visual Studio and Development Technologies MVP with C++ focus) says:

        But to answer your question, no, this approach is insufficient for .NET tracking handles. In particular, the second parameter to InterlockedExchangePointer is passing a pointer *by-value*, and the garbage collector isn’t aware of this copy, because it’s a native type, exposing a race condition. Should the garbage collector run between argument setup and the actual interlocked instruction, and moves the object, it won’t update the by-value copy of the pointer, and the result is a dangling tracking handle.
        I’m not sure how System::Threading solves this, it could either be by forcing inclusion of the intermediate copies in the metadata used by the garbage collector, by suspending garbage collection for the length of the function call, or pinning the objects which are targets of the tracking handles being exchanged.

        1. Medinoc says:

          Thanks for this info.

  2. Pierre B. says:

    My main concern with the code is that it’s a template and it could be inlined. Once inlined, and given that the pointers are being reinterpret_cast, I’d be afraid of what conmpiler optimization could go wrong, given that the change of bits is hidden from teh compiler.

    Imagine the following code:

    XYZ^ ptr = …;

    if (ptr) { … } // now compiler can assume to already know if ptr is null or not.

    InterlockedExchange(…); // inlined, so compiler “knows” the implementation but some of it uses reinterpret_cast

    if (ptr) { … } // Maybe compiler optimize this second null check?

    1. Joshua says:

      In theory, but not in any scope you care about. The call chain eventually calls InterlockedExchange, which can’t be inlined and for which the compiler must assume can do anything to stuff at global scope. So all you have to do is make sure it isn’t a stack reference (or what’s the point of InterlockedExchange). Static doesn’t put you back to disaster because the compiler can’t know InterlockedExchange isn’t mutually recursive with you.

      1. alegr1 says:

        InterlockedExchange is an intrinsic function, but the compiler knows not to optimize it any further.

  3. Wear says:

    Man those /CX posts have some fun comments.

    1. David Haim says:

      Microsoft, about year 2010:
      Manager: “OK guys, focus! what do we know about C++ developers?”
      Worker A:”They’re religious about their practices and they hate anything that even remotely resemble manged code”
      Manager: “OK great, what else? time is wasting guys!”
      Worker B: “They hate COM so bad they prefer roll their own implementation instead”
      Manager: “OK then, here’s an idea: will take good old COM, and extend C++ with ugly symbols to support COM, and it will look like F****** C#!”
      Workers: clamp heavily.

      1. alegr1 says:

        Don’t you mean year 2001?

  4. Neil says:

    Disabling template deduction for value would be another possibility. Or you could use std::enable_if and std::is_convertible if you’re worried about overload resolution.

  5. cheong00 says:

    I’ll admit that when I see the hat(^) thing, I originally thought that was an addition to managed C++ code.

    You see, most of the variable declarations there are in form of “Message^ h_Message = gcnew Message;”

Comments are closed.

Skip to main content