Is a global container problematic? [SOLVED]



  • On 27/03/2015 at 05:55, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R14-R16 
    Platform:      
    Language(s) :     C++  ;

    ---------
    Hi!

    Imagine you have a global container, say a HashMap, in your code. Is it problematic when it contains
    any data that it will need to free in its destructor when the dynamic library is unloaded? Because that
    will certainly happen after  PluginEnd().

    If there will be any issues, is it enough to use HashMap::Reset() in PluginEnd()?

    Thanks in advance
    Niklas



  • On 27/03/2015 at 09:43, xxxxxxxx wrote:

    Hi Niklas,

    I'd like to help you, but I don't have enough information yet to determine what situation you are in.  Specifically, the HashMap holds what type of data?  If it's simple data or even a struct that only contains simple data (integers, chars, floats, bools) then you shouldn't have any problems just using HashMap::Reset().  However, it can be insufficient if it contains:

    - Objects that may contain dynamic memory
    - Objects that use objects that contain dynamic memory
    - Pointers to any kind of data, whether simple or objects of the kind mentioned above

    It all depends on the specific implementation of each part.  HashMap::Reset() is enough in some cases, but not all, especially if it contains pointers: pointers themselves do not get destroyed because they only contain address values, but what they point to has to be destroyed to avoid problems.

    Let me know the details of each case (if it's more than one HashMap), and we'll figure it out.

    Joey Gaspe
    SDK Support Engineer



  • On 29/03/2015 at 09:48, xxxxxxxx wrote:

    Hi Niklas,
    here is simple test code for this case.
    So I think this should work without problems.

      
        
        
        #include "c4d.h"
        #include "c4d_misc.h"
         
        //=====================================================================================================================
        struct GlobalData
        {
           //! it is not allowed to use C4d API in the global constructor because t_C4DOS is still NULL!
           GlobalData() {  }
          ~GlobalData() { log("~GlobalData",t_C4DOS); }
         
        };
        //=====================================================================================================================
        struct TestData
        {
           TestData() { log(" TestData"); }
           TestData(const String &d) : m_data(d) { log(" TestData "+d); }
          ~TestData() { log("~TestData: "+m_data, t_C4DOS); }
         
          String m_data;
        };
         
        GlobalData	           g_data;
        maxon::BaseArray<TestData> g_stringArray;
         
        //######################################################################################################################
        Bool PluginStart()                  
        {
          log("PluginStart");
         
          g_stringArray.Append(String("Test1"));
          //g_stringArray.Append(String("Test2"));
         
          return TRUE;
        }
        //######################################################################################################################
        void PluginEnd()
        {
          log("PluginEnd");
        }
        //######################################################################################################################
        Bool PluginMessage(Int32 id, void *data)
        {
          log("PluginMessage: ",to_c_string(id),"			",t_C4DOS);
         
          return TRUE;
        }
    

    And here is the C4D Start and C4D end output.

      
    Cinema 4D R16 start sequence >>>                       <C4D API Available>  
        
    Global Constructor                                      NO (t_C4DOS is NULL)  
    PluginMessage:  C4DMSG_PRIORITY<0>                      Yes  
    PluginMessage:  C4DPL_INIT_SYS<1>                       Yes  
    PluginMessage:  C4DPL_PYINITTYPES<1027123>              Yes  
    PluginMessage:  C4DPL_PYINITIALIZE<1026661>             Yes  
    PluginStart:      
     TestData  Test1      
    ~TestData: Test1 0x00007FFB2E7DB6A0      
        
    PluginMessage:  C4DPL_REGISTERPYPLUG<1026805>           Yes  
    PluginMessage:  C4DPL_STARTACTIVITY<1000>               Yes  
    PluginMessage:  C4DPL_BUILDMENU<1001188>                Yes  
    PluginMessage:  C4DPL_LAYOUTCHANGED<300002164>          Yes  
    PluginMessage:  C4DPL_PROGRAM_STARTED<450000215>        Yes  
    PluginMessage:  C4DPL_COMMANDLINEARGS<1002>             Yes  
    PluginMessage:  ???<450000234>                          Yes  
        
    # Cinema 4D R16 end sequence >>>      
        
    PluginMessage:  ???<200000238>                          Yes  
    PluginMessage:  C4DPL_ENDPROGRAM<1001084>               Yes  
    PluginMessage:  C4DPL_SHUTDOWNTHREADS<300002148>        Yes  
    PluginMessage:  C4DPL_ENDACTIVITY<1001>                 Yes  
    PluginMessage:  C4DPL_PYENDPLUGINS<300002146>           Yes  
    PluginMessage:  C4DPL_ENDPLUGINACTIVITY0<1026848>       Yes  
    PluginMessage:  C4DPL_PYFINALIZE<1026662>               Yes  
    PluginMessage:  C4DPL_ENDPLUGINACTIVITY1<200000272>     Yes  
    PluginMessage:  C4DPL_ENDPLUGINACTIVITY2<200000276>     Yes  
    PluginEnd:      
        
    ~TestData: Test2                                         Yes  
    ~TestData: Test1                                         Yes  
    ~Global Destructor                                       Yes  
      
    


  • On 10/04/2015 at 12:46, xxxxxxxx wrote:

    Hi,

    Due to the lack of activity in this topic, I'll mark it as solved.

    Joey Gaspe
    SDK Support Engineer



  • On 11/04/2015 at 11:49, xxxxxxxx wrote:

    Hi,
    so you can confirm that this is safe ? :)

    Remo



  • On 12/04/2015 at 08:51, xxxxxxxx wrote:

    Hi, sorry for my inactivity.

    Originally posted by xxxxxxxx

    Hi Niklas,

    I'd like to help you, but I don't have enough information yet to determine what situation you are in.  Specifically, the HashMap holds what type of data?  If it's simple data or even a struct that only contains simple data (integers, chars, floats, bools) then you shouldn't have any problems just using HashMap::Reset().  However, it can be insufficient if it contains:

    - Objects that may contain dynamic memory
    - Objects that use objects that contain dynamic memory
    - Pointers to any kind of data, whether simple or objects of the kind mentioned above

    It all depends on the specific implementation of each part.  HashMap::Reset() is enough in some cases, but not all, especially if it contains pointers: pointers themselves do not get destroyed because they only contain address values, but what they point to has to be destroyed to avoid problems.

    Let me know the details of each case (if it's more than one HashMap), and we'll figure it out.

    Joey Gaspe
    SDK Support Engineer

    I see three different situations similar to what you've mentioned already:

    1. The HashMap contains a simple data type that does not require explicit deallocation or do not
      perform such in its destructor
    2. The HashMap contains a data type that deallocates memory using GeFree() in its destructor
    3. The HashMap contains a pointer that must manually be freed before calling HashMap::Reset()

    For #3, it's crystal clear to do the deallocation in PluginEnd() at the very least and then call Reset()
    (although it might not be necessary, it's definitely a good choice to do it after the deallocation of
    all elements).

    For #2, if you do not call HashMap::Reset() in PluginEnd() but instead let the HashMap destructor
    handle it, the time at which the destructors are called will be after PluginEnd().

    For #1, this a concern that I also have for #2, the HashMap must deallocate its elements/nodes/entries
    that contain the key/value pairs. Again if Reset() is not called in PluginEnd(), the HashMap will be 
    destroyed when the DLL/DYLIB is unloaded.

    What it basically breaks down to is the following: Can GeFree() still be called after PluginEnd(),
    or at the time the DLL/DYLIB is unloaded?

    The reason I ask is because you can not before PluginStart() because the C4DOS is not initialized. If
    you have a global String  (not pointer, just a plain String object), your plugin will not load with (at
    least on Windows) a "pure virtual function call" error window, because the String constructor, which
    is called when the DLL/DYLIB is loaded, before PluginStart(), will use the C4DOS function table.

    @Remo: Thanks for the test, but from your output you didn't show if C4DOS was still valid
    when GlobalData was destroyed? I just want an official statement know if that is intended that it
    will work without a crash or if that might change at any time. :)

    -Niklas



  • On 12/04/2015 at 11:43, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    [...] What it basically breaks down to is the following: Can GeFree() still be called after PluginEnd(),
    or at the time the DLL/DYLIB is unloaded?
    The reason I ask is because you can not before PluginStart() because the C4DOS is not initialized. If
    you have a global String  (not pointer, just a plain String object), your plugin will not load with (at
    least on Windows) a "pure virtual function call" error window, because the String constructor, which
    is called when the DLL/DYLIB is loaded, before PluginStart(), will use the C4DOS function table.
    @Remo: Thanks for the test, but from your output you didn't show if C4DOS was still valid
    when GlobalData was destroyed? I just want an official statement<span style="line-height: 1.4;"> know if that is intended that it</span>
    <span style="line-height: 1.4;">will work without a crash or if that might change at any time. :)</span>
    -Niklas

    The important question shouldn't be if can be done, but if it should be done. The docs about PluginEnd() state:

    "void PluginEnd(void)
    This is called when the plugin is unloaded from CINEMA 4D. Here you should free your plugin registrations and any resources which are not owned or already freed by other plugins (see PluginMessage())."

    If you're a "good citizen" then you're freeing global data (that you've allocated) at PluginEnd(). Freeing global data on DLL/dylib unloading is too late - there 's no guarantee that this will work without a crash.

    Furthermore: If you're circumventing Cinema's threading class (e.g. utilizing pthreads or Windows threads directly), be aware that you can create nasty crash bugs - e.g. by using thread local variables (that you've created via pthread_key_create()/FlsAlloc()). If you don't release these variables before your plugin is unloaded, then the OS will try to access (and delete) them at some undetermined moment in time after the DLL/dylib and all of its memory is already released, which will result in crashes that are pretty hard to track down.

    Best regards,

    Wilfried



  • On 13/04/2015 at 09:08, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    @Remo: Thanks for the test, but from your output you didn't show if C4DOS was still valid
    when GlobalData was destroyed? I just want an official statement know if that is intended that it
    will work without a crash or if that might change at any time. :)

    Yes it is still valid, you can call GePrint, write to the files and of course call GeFree. (at least in my test with R16)

    Any way I would recoment to free all the date at least in PluginEnd() or may be even earlier in
    PluginMessage(C4DPL_ENDPLUGINACTIVITYx) .
    This is especially important for C4DThreads and BaseContainers.

    I think it should be OK to call GeFree on a memory buffer from Global Destructor after PluginEnd().


Log in to reply