none
Where should a third-party library be terminated when implementing a COM class? RRS feed

  • Question

  • I'm implementing a Windows property handler shell extension using ATL.  Internally, my property handler class relies on a third-party library that must be explicitly initialized and terminated. Based on advice received in another thread, I'll be initializing the third-party library in my class's FinalConstruct() method, using static local variable initialization, like so:

    static int init = (InitializeLibrary(), 0);

    This guarantees that the library initialization takes place only once, even across threads.

    Now I'm wondering where to terminate the library.  I don't think the termination call can simply go in FinalRelease(), because that would terminate the library whenever an object instance is destroyed, even when other objects still exist.  Right?  So is there a safe place to terminate?  Would it have to go somewhere in DLLCanUnloadNow()?  Does ATL have an object count that I can follow, and then terminate the library it when the counter hits zero?  Thank you for any guidance.

    Also, the library I'm using, Adobe's XMP SDK toolkit, says this in the documentation:

    The functions in XMPCore and XMPFiles are thread safe. You must call the initialization and termination functions in a single-threaded manner; between those calls, you can use threads freely, following a multi-read, single-writer locking model. All locking is automatic and transparent.

    Any thoughts?


    • Edited by amt528 Monday, October 14, 2019 5:25 PM
    Monday, October 14, 2019 2:48 PM

All replies

  • An in-process (Dll) COM Server created by the ATL wizard and derived from CAtlDllModuleT will contain an exported DllCanUnloadNow() function that calls ATL's implementation.

    	HRESULT DllCanUnloadNow() throw()
    	{
    		T* pT = static_cast<T*>(this);
    		return (pT->GetLockCount()==0) ? S_OK : S_FALSE;
    	}
    

    So in your DllCanUnloadNow() function could look like this -

    // Used to determine whether the DLL can be unloaded by OLE.
    STDAPI DllCanUnloadNow(void)
    {
    	HRESULT hr = _AtlModule.DllCanUnloadNow();
    	if (hr == S_OK)
    	{
    		//Uninitialize your library
    	}
    
    	return hr;
    }
    

    Monday, October 14, 2019 3:32 PM
  • Thank you for your response.  I'm trying out your proposed method, but unfortunately, it's causing my handler to consistently crash.  Is it possible that there are timing issues when terminating the library within DLLCanUnloadNow?

    Also, the library I'm using, Adobe's XMP SDK toolkit, says this in the documentation:

    The functions in XMPCore and XMPFiles are thread safe. You must call the initialization and termination functions in a single-threaded manner; between those calls, you can use threads freely, following a multi-read, single-writer locking model. All locking is automatic and transparent.

    Does that provide any clues?

    • Edited by amt528 Monday, October 14, 2019 4:53 PM
    Monday, October 14, 2019 4:46 PM
  • If COM does not immediately unload your library after DllCanUnloadNow() returns it is possible for another instance of your COM class to be created.  In that case, FinalConstruct will think that the library is initialized (even though it is not) because the local static variable was never reset.

    The Adobe documentation sounds to me like the initialization and termination functions must be called from the same thread.  That may not be happening.

    • Edited by RLWA32 Monday, October 14, 2019 5:03 PM
    Monday, October 14, 2019 4:58 PM
  • Thanks again for your response.  So does that mean I can't use the local static variable approach?  Or is it still possible?  Do you have any other suggestions on how to cleanly initialize and terminate the library?
    Monday, October 14, 2019 5:03 PM
  • First, I suggest you do a test to find out if the Adobe library's initialization and termination functions must run on the same thread.

    Monday, October 14, 2019 5:10 PM
  • You can hook into ATL module termination with CAtlModule::AddTermFunc . A singleton instance of CAtlModule is accessible via _pAtlModule global variable.

    Igor Tandetnik

    Monday, October 14, 2019 9:48 PM
  • Thanks. So is this correct?

    void __stdcall TerminationCallback(DWORD_PTR dw) {
        SXMPFiles::Terminate();
    }
    HRESULT CMyPropertyHandler::FinalConstruct() {
        static BOOL init = SXMPFiles::Initialize();
        static HRESULT destruct = _pAtlModule->AddTermFunc(TerminationCallback, NULL);
        return S_OK;
    }
    Actually, that must be wrong, because I'm getting a crash and some sort of error in xtree.



    • Edited by amt528 Tuesday, October 15, 2019 2:14 AM
    Monday, October 14, 2019 11:12 PM
  • Are you using the Apartment threading model for your shell extension?

    Did you resolve the thread affinity question for the Adoble library's initialization and termination functions?

    Tuesday, October 15, 2019 11:34 AM
  • Yes, I'm using Apartment as the threading model.  I'm not sure about the thread affinity question, and I'm not sure how to test that.  The Adobe docs say, "You must call the initialization and termination functions in a single-threaded manner."  Does that mean it requires thread affinity?  Thanks again for your help.
    Tuesday, October 15, 2019 12:59 PM
  • Well, you could write a minimal program that initializes the library on its main thread, does some work, and then starts another thread that calls the library's termination function.  Observe the results.

    In FinalConstruct call GetCurrentThreadId to get the id of the thread on which the library's initialization function is running.

    Then, wherever you call the library's termination function in your shell extension call GetCurrentThreadId again. Does the id of the thread on which the termination function is called match the id of the initialization thread?

    Tuesday, October 15, 2019 1:10 PM
  • On a Windows 7 system I build the SDK RecipePropertyHandler sample.  It's not an ATL project, but I made sure the threading model was Apartment and inserted some debugging statements to display thread  ids in the COM object's constructor, destructor, and in COM server's DllCanUnloadNow function.  To avoid thread id reuse issues I used CoGetCurrentProcess function instead of GetCurrentThreadId.

    Then I opened explorer, navigated to a folder containing the .Recipe file and selected it.  Explorer loaded the extension.  Then I closed explorer and the extension was unloaded.

    This is what the debugging statements showed.

    [1512] In CRecipePropertyHandler::CRecipePropertyHandler thread id is 81
    [1512] In CRecipePropertyHandler::~CRecipePropertyHandler thread id is 81
    [1512] In DllCanUnloadNow thread id is 82
    [1512] In DllCanUnloadNow thread id is 81
    

    Note the multiple calls to DllCanUnloadNow and that they didn't always occur on the same thread used for the COM object.

    So I suggest you capture the thread id in FinalConstruct when you call the Adobe library's initialization function and make sure that you don't call the Adobe library's termination function unless your code is running on that same thread.

    Tuesday, October 15, 2019 3:04 PM
  • Wow, thank you for conducting that test.  I'll try to do something similar.  For me it raises a few questions: (1) if I'm calling the initialize function using that local static assignment technique within FinalConstruct(), how would I simultaneously capture the thread id--in a variable that would be accessible from DllCanUnloadNow?  And also (2) what if DllCanUnloadNow is never called from the same thread?  Do we know that it eventually will be?  Also, (3) Would it be possible to combine the thread-capture strategy with CAtlModule::AddTermFunc()?   Thanks again for all your help.




    • Edited by amt528 Tuesday, October 15, 2019 3:24 PM
    Tuesday, October 15, 2019 3:16 PM
  • In general, it would be impossible to guarantee that a) the library is initialized when the first COM object instance is constructed, b) the library is deinitialized when the last instance is destroyed, and c) these two happen on the same thread.

    It is possible for your COM client to produce this sequence of events:

    1. Thread A creates an instance X of your object
    2. Thread B creates an instance Y
    3. Thread A releases X
    4. Thread A terminates
    5. Thread B releases Y

    The library must be alive from 1 through 5, but at point 1 thread B doesn't exist yet, and at point 5 thread A doesn't exist already. There may not be a thread that exists for the whole lifetime of the library.


    Igor Tandetnik

    Tuesday, October 15, 2019 11:17 PM
  • Well is calling these functions from DllMain() an option?  I certainly don't want to do that, because (1) it seems like calling things from DllMain() is complicated and generally a bad idea and violates lazy initialization; and (2) my DLL contains other shell extension handlers that don't rely on this particular third-party library, so placing the library calls in DllMain() would potentially result in unnecessary initialization/termination.

    On the other hand, I'm not sure it would necessarily violate the best practices.  DllMain() also receives some thread-related messages that seem like they might come in handy.  Do you have any thoughts?

    Also, what if I just didn't call the termination function?  Will that necessarily cause a memory leak, or could it be okay?





    • Edited by amt528 Wednesday, October 16, 2019 1:46 PM
    Wednesday, October 16, 2019 1:31 PM
  • How about using your own thread to control the library initialization/termination functions?

    Wednesday, October 16, 2019 2:08 PM
  • Unfortunately I'm not sure how to do that.

    For the time being, I've settled on doing this in DllMain:

    extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
        if (dwReason == DLL_PROCESS_DETACH) {
            SXMPFiles::Terminate();
        }
        // ...
    }

    I know it's not best practice or thread-safe, and hell, I don't even know if that terminate() call is even working, but it seems to be only the thing that avoids crashing my program.  I'll revisit the issue once I understand more about threading, DLLs, the WinAPI, etc.  Thank you very much for your help.




    • Edited by amt528 Wednesday, October 16, 2019 5:26 PM
    Wednesday, October 16, 2019 5:25 PM
  • Hi,

    Thank you for posting here.

    According to your issue , I suggest you should ask it in Adobe Support Community Forum. And this thread will be moved to Off-Topic Posts.


    Best Regards,

    Suarez Zhou

           
    Thursday, October 17, 2019 2:55 AM