samedi 16 juin 2018

Using thread_local with a COM interface in a DLL

The Scenario

Given the following code fragment from a project that builds to a DLL:

inline ShellItem2Pointer get_desktopItem()
{
    ShellItem2Pointer ret;
    auto desktopSf = desktopFolder(); //a similar function that calls SHGetDesktopFolder
    ::SHGetItemFromObject(desktopSf.asUnknown(), IID_PPV_ARGS(ret.addressOf()));
    Q_ASSERT(ret);
    return ret;
}

//! Retrieves a `ShellItem2Pointer` for the desktop.
QE_WINDOWS_EXPORT ShellItem2Pointer desktopItem()
{
    static ShellItem2Pointer ret = get_desktopItem();
    return ret;
}

Where ShellItem2Pointer is a specialization of yet-another basic smart pointer class for IUnknown interfaces. The specialization is for UnknownPointer<IShellItem2>, and it provides the usual basic automatic AddRef() and Release() calls plus some helper functions, including asUnknown() and addressOf().

In context, the main thread maintains a cache of data from the Shell namespace to display in a tree or list view. Icons, thumbnails, and data for remote objects will be loaded in background threads (ITEMIDLISTs are copied and safely passed between threads instead of IShellItem2Pointers).

The question

If I change the declaration of the static variable to

thread_local ShellItem2Pointer ret ...

to ensure my threads aren't sharing a COM object improperly, what pitfalls are there and what protections must I assume regarding the change? I am looking both from the perspective of thread_local variables in general in DLLs, as well as the fact that it holds a pointer to an IShellItem2.

Things we already know because we went on MSDN

  • Dynamically initialized thread-local variables in DLLs may not be correctly initialized on all calling threads. [not applicable, but good to know]
  • ...
  • You can specify thread_local only on data items with static storage duration. This includes global data objects (both static and extern), local static objects, and static data members of classes. Any local variable declared thread_local is implicitly static if no other storage class is provided; in other words, at block scope thread_local is equivalent to thread_local static.

  • ...

  • On Windows, thread_local is functionally equivalent to __declspec(thread) except that [compiler-specific extensions]...

And from the docs for __declspec(thread):

If the variable is initialized with a function call (including constructors), this function will only be called for the thread that caused the binary/DLL to load into the process, and for those threads that started after the binary/DLL was loaded. The initialization functions are not called for any other thread that was already running when the DLL was loaded. Dynamic initialization occurs on the DllMain call for DLL_THREAD_ATTACH, but the DLL never gets that message if the DLL isn't in the process when the thread starts.

Thread-local variables that are initialized statically with constant values are generally initialized properly on all threads. However, as of December 2017 there is a known conformance issue in the Microsoft Visual C++ compiler whereby constexpr variables receive dynamic rather than static initialization. [also not applicable, also good to know]

Note: Both of these issues are expected to be fixed in future updates of the compiler.

I dug up a good overview of the subject, but it dates to 2007. The Old New Thing also has a good article that's too dated, as well.

Is this a dupe?

I've found several questions that have answers related to this topic, but I am unable to find an SO question that directly answers my question. A sample:

Aucun commentaire:

Enregistrer un commentaire