BaseContainer FlushAll crashes



  • In continuation from the disussion concerning ToolData container, I am encountering an issue I cannot seem to put my finger on. So I extracted the offending code into a separate dummy plugin.

    The whole code is below.
    A command data when executed will parse the Live Selection description and store all settings into a basecontainer, which is located in a separate class.

    When Cinema 4D is then closed the class hosting the basecontainer is destroyed, which calls the basecontainer's destructor, which will perform a FlushAll.
    For one reason or another this call crashes.

    I have pinpointed the issue to the SplineData which is part of the soft-selection settings of the Live Tool (while not actively shown this attribute is still part of the description).
    This SplineData is a custom data type, and when I omit copying this attribute into the test basecontainer all goes well when class is destroyed.

    To me, it seems as if copying the data into the container does not make a copy, but uses the original pointer. Hence, when the test class and its test basecontainer is destroyed it will try to destroy the SplineData ... which has already been destroyed as a result of Cinema 4D closing (... at least, that's my guess).

    If I store the settings into a basecontainer of the command data then there seem to be no problem.
    Which does make me think I am doing something wrong with my test class allocation/deallocation.
    I just cannot seem to see what exactly it is I am doing wrong.

    Tried with R16, R17, ...

    // ========================
    // Testing
    // Dummy "empty" plugin
    // ========================
    
    #include "c4d.h"
    
    // Dummy IDs - for demonstration purposes only
    #define MYCOMMAND_PLUGIN_ID	1999999
    
    class Test
    {
    public:
    	Test() {}
    	virtual ~Test() {}
    
    	BaseContainer mTestContainer;
    };
    
    Test* gTest = nullptr;
    
    // ====================================
    // CommandData
    // ====================================
    
    class MyCommand : public CommandData
    {
    	INSTANCEOF(MyCommand, CommandData)
    
    public:
    	virtual Bool Execute(BaseDocument* doc);
    	virtual Bool ExecuteSubID(BaseDocument* doc, Int32 subid);
    	virtual Int32 GetState(BaseDocument* doc);
    	virtual Bool RestoreLayout(void* secret);
    	virtual Bool Message(Int32 type, void* data);
    
    //	BaseContainer	mToolData;
    };
    
    Bool MyCommand::Execute(BaseDocument* doc)
    {
    	// get the live selection description
    	const Int32 id = ID_MODELING_LIVESELECTION;
    	BasePlugin* plug = FindPlugin(id, PLUGINTYPE_TOOL); // needs explicitely to be PLUGINTYPE_TOOL, as PLUGINTYPE_ANY does not work correctly
    	if (!plug)
    		return FALSE;
    
    	AutoAlloc<Description> desc;
    	if (desc && plug->GetDescription(desc, DESCFLAGS_DESC_0))
    	{
    		void *handle = desc->BrowseInit();
    
    		DescID dcid, groupid;
    		GeData gd;
    		const BaseContainer *objBc = NULL;
    		while (desc->GetNext(handle, &objBc, dcid, groupid))
    		{
    			if (objBc) {
    				const String name = objBc->GetString(DESC_NAME);
    				const Int32 id = dcid[0].id;
    
    				plug->GetParameter(DescID(dcid), gd, DESCFLAGS_GET_0);
    				const Int32 type = gd.GetType();
    				// attributes can be buttons, bars, ...
    				// anything without a user input value, skip these
    				if (type == DA_NIL)
    					continue;
    				
    				//mToolData.SetData(id, gd);
    				gTest->mTestContainer.SetData(id, gd);
    			}
    		}
    		desc->BrowseFree(handle);   //Free the memory used by the Browse function  
    	}
    
    	return TRUE;
    }
    
    Bool MyCommand::ExecuteSubID(BaseDocument* doc, Int32 subid)
    {
    	return TRUE;
    }
    
    
    Int32 MyCommand::GetState(BaseDocument* doc)
    {
    	return CMD_ENABLED;
    }
    
    Bool MyCommand::RestoreLayout(void* secret)
    {
    	return TRUE;
    }
    
    Bool MyCommand::Message(Int32 type, void* data)
    {
    	return SUPER::Message(type, data);
    }
    
    Bool RegisterMyCommand(void)
    {
    	return RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Testing", 0, AutoBitmap("icon.png"), "Test", NewObjClear(MyCommand));
    }
    
    // ====================================
    // Plugin Main 
    // ====================================
    Bool PluginStart(void)
    {
    	RegisterMyCommand();
    
    	gTest = NewObjClear(Test);
    	if (!gTest)
    		return FALSE;
    
    	return TRUE;
    }
    void PluginEnd(void) 
    {
    	if (gTest)
    	{
    		gTest->mTestContainer.FlushAll(); // This line was added for testing out the flushing -> it crashes ... why?
    		// without the above line, the destructor of the Test class would call BaseContainer's destructor, which would perform a FlushAll -> this crashes
    
    		DeleteObj(gTest);
    	}
    }
    Bool PluginMessage(Int32 id, void * data)
    {
    	switch (id) {
    	case C4DPL_INIT_SYS:
    		if (!resource.Init())
    			return FALSE;
    		return TRUE;
    	case C4DMSG_PRIORITY:
    		return TRUE;
    	case C4DPL_BUILDMENU:
    		break;
    	case C4DPL_ENDACTIVITY:
    		return TRUE;
    	}
    	return FALSE;
    }
    
    

  • Global Moderator

    Hi @C4DS

    You should free your stuff in C4DPL_ENDACTIVITY.

    Documentation of PluginEnd will be improved to point to C4DPL_ENDACTIVITY, in any case, see Plugin Functions Manual.

    Cheers,
    Maxime.



  • Seems I have been doing it wrong for all those years.
    Thanks for leading me onto the right path ...