Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Is a global container problematic? [SOLVED]

    SDK Help
    0
    8
    552
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • H
      Helper
      last edited by

      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

      1 Reply Last reply Reply Quote 0
      • H
        Helper
        last edited by

        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

        1 Reply Last reply Reply Quote 0
        • H
          Helper
          last edited by

          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  
            
          
          1 Reply Last reply Reply Quote 0
          • H
            Helper
            last edited by

            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

            1 Reply Last reply Reply Quote 0
            • H
              Helper
              last edited by

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

              Hi,
              so you can confirm that this is safe ? 🙂

              Remo

              1 Reply Last reply Reply Quote 0
              • H
                Helper
                last edited by

                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

                1 Reply Last reply Reply Quote 0
                • H
                  Helper
                  last edited by

                  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

                  1 Reply Last reply Reply Quote 0
                  • H
                    Helper
                    last edited by

                    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().

                    1 Reply Last reply Reply Quote 0
                    • First post
                      Last post