Search code examples
c++winapicommutexatl

How to use a mutex/critical section within a member function inside a DLL


I'm creating a COM class (with ATL) that resides inside a DLL. For one of my member functions, depending on certain conditions, I'd like to potentially use a third-party library (Adobe's XMP SDK), which must be initialized and terminated. So basically, I'll have a member function which looks sort of like this:

void CMyClass::MyMemberFunction()
{
    SXMPMeta::Initialize();

    // ...

    SXMPMeta::Terminate();
}

Now according to the Adobe XMP library docs,

You must call the initialization and termination functions in a single-threaded manner . . . .

Meanwhile, I believe that File Explorer may create multiple instances of my class on separate threads. So if I understand things correctly, it sounds like I'll have a critical section and I'll need to use mutexes around the library initialization and termination (but please correct me if that's wrong).

I'm unclear on how to go about this. I wasn't sure if I should use one of the ATL critical section classes or perhaps CRITICAL_SECTION. CRITICAL_SECTION seemed like a nice option, but the example shows it being initialized in main(). What happens if you're in a DLL? I'd prefer not to start messing with DllMain(). Any help or thoughts would be appreciated.

Per Gem's suggestion, I tried the following:

struct XMPLibraryInitializer
{
    XMPLibraryInitializer()
    {
        // Initialize libraries
        if (!SXMPMeta::Initialize() || !SXMPFiles::Initialize())
        {
            XMP_StringPtr pszErrorMessage = "Libraries failed to load";
            throw XMP_Error(kXMPErr_InternalFailure, pszErrorMessage);
        }
        ATLTRACE("\nXMP library initialized on thread %lu\n", GetCurrentThreadId());
    }
    ~XMPLibraryInitializer()
    {
        // Terminate XMP libraries
        SXMPFiles::Terminate();
        SXMPMeta::Terminate();
        ATLTRACE("\nXMP library terminated on thread %lu\n", GetCurrentThreadId());
    }
};

HRESULT MyFunc()
{
    // Statically initialize the Adobe XMP library
    try
    {
        static XMPLibraryInitializer xmpLibraryInitializer;
    }
    catch (const XMP_Error & e)
    {
        return E_UNEXPECTED;
    }

    // ...

}

This seems to operate fine, except the output I see is

XMP library initialized on thread 5820 ... XMP library terminated on thread 3104

Is there any explanation for the different thread numbers? And if the numbers are different, does that mean it doesn't comply with the docs requiring single threaded initialization and termination?


Solution

  • A simple static singleton function should suffice, if you want to keep hold of it until the program exits. The static initialisation is guaranteed to be only done one in an efficient thread-safe manner (from C++11) usually involving a mutex double-check of a guard flag.

    You would call it every place where you can't be sure you already have the ATX set up. There is no point, but no harm, in calling it in the middle of some repeated transaction. So do call it in GiveMeAToken() but don't call it in UseToken() because you know it must have been called in order to get the token?

    I updated this to show how a simple bool Initialise success status can be kept, though the actual return from SXMPMeta::Initialize() may be more complex:

    struct SomeATXThing
    {
        bool good;
        SomeATXThing()
        {
            good = SXMPMeta::Initialize();
        }
        ~SomeATXThing()
        {
            if (good)
                SXMPMeta::Terminate();
        }
    };
    
    SomeATXThing* AtxEnsureSingleton()
    {
        static SomeATXThing someATXThing;
        return someATXThing.good ? &someATXThing : nullptr;
    }
    

    Note one caveat with singletons is that they are destructed in reverse order to the end of their construction. Some other independently earlier constructed singleton MUST NOT rely on the existence of this singleton during its destruction, but it is safe for one singleton constructor to invoke another during construction and again during destruction. There is no easy enforcement of this rule, except ABEND.

    You can do phoenix reactivation where the object (generally relying on undefined behaviour existence of object instance addresses after destruction), but it is a load of grief.