SOLVED Replace Plugins in a Running Cinema 4D Instance

Hello @ferdinand
Thank you for a such detailed answer.
I've pasted your code in my project but received the error during compiling when I call SomeFunction() in another place of project.

So I've changed it little bit to this and now it works perfectly:

void DownloadUpdates()
{
    iferr_scope_handler
    {
        GePrint(err.GetMessage());
        return;
    }; 
     
    const maxon::Url url("https://mysite.com/somefile.jpg"_s);
  
    const maxon::UrlScheme scheme = url.GetScheme();
    const maxon::Bool isHTTP = scheme == maxon::URLSCHEME_HTTP;
    const maxon::Bool isHTTPS = scheme == maxon::URLSCHEME_HTTPS;

    if (isHTTP || isHTTPS)
    {
        const maxon::InputStreamRef inputStream = url.OpenInputStream() iferr_return;
        const maxon::Int length = inputStream.GetStreamLength() iferr_return;

        maxon::BaseArray<maxon::Char> data;
        data.Resize(length) iferr_return;

        inputStream.Read(data) iferr_return;
        inputStream.Close() iferr_return;

          
        const maxon::Url someUrl(GeGetPluginPath().GetString()); 
        const maxon::Url anotherUrl = (someUrl + url.GetName()) iferr_return;
        const maxon::OutputStreamRef outputStream = anotherUrl.OpenOutputStream() iferr_return;

        outputStream.Write(data) iferr_return;
        outputStream.Close() iferr_return;

    }
}  

Thank you.

One last question: does the way to rewrite the .xdl64 plugin file during Cinema 4D is going to work exist? (OR at least replace it after Cinema 4D was closed?)
Or I will be needed to ask user to install updates manually?

From your C++ plugin call Python code to install your new plugin. Python doesn’t care if the file is in use so you can do what you want to it. I personally rename the existing ones then install the new one and when C4D is shut down I delete any renamed files.

Hello @yaya,

please remember to open a new topic for new questions. I have forked your question, since it had factually not much to do with the old thread.

@yaya said in OpenInputStream() works incorrect.:

One last question: does the way to rewrite the .xdl64 plugin file during Cinema 4D is going to work exist? (OR at least replace it after Cinema 4D was closed?)
Or I will be needed to ask user to install updates manually?

Hm, I am not 100% sure if I do understand you correctly here, but I assume you want to hot-swap a plugin file while Cinema is running. You cannot do this at any point, because the OS will deny write access when the file is still in use by Cinema 4D. And the SDK also did never offer such functionality.

Your best bet would be to do this in Plugin Message() when C4DPL_ENDPLUGINACTIVITY2 is being emitted. For all prior messages the plugins are still registered, i.e., Cinema 4D might have still the files loaded. But be aware: With C4DPL_ENDPLUGINACTIVITY2 you are deep in the shut-down process of Cinema 4D, which means you cannot handle anything that is a plugin (many custom data types are for example) and also stuff like the active document is not available anymore. If you plan on using the maxon API, relevant registries might have been already shut down at this point too. Your best bet is here to use as much of the std library as possible for the writing of files.

I haven't done this myself yet, but the way I would try, is to use our APIs to download and unpack the data with while Cinema is running. And then store that data as a Char* or something like that. When C4DPL_ENDPLUGINACTIVITY2 is then being emitted, you can write the data. You can restart Cinema 4D with RestartApplication(), i.e., after you are done downloading, etc., you can invoke a restart with that function, so that the plugin can be replaced. Depending on what you use in std, you might have to provide different code for Windows and MacOS.

Cheers,
Ferdinand

Thank you @ferdinand . Sorry for my confused explanation, but yes, you understand perfectly correctly: I need to update the plugin file on the clients machine when the update-file is ready on my web-server.
Right now I am able to send the message into my plugin instance from my server. My plugin listening my web-server and in some moment receives the message that the update is available. When plugin instance got this message, it starts the code function quoted above (i.e download the new .xdl64 file).

So I can build the updates system through the notification to the client with the request to install this update manually. But I want to make it more easily and unnoticed by the client.

Right now I've tried to simple replace my plugin-file (.xdl64) during C4DPL_ENDPLUGINACTIVITY2 via

std::filesystem::copy_file(from, to, overwrite_existing);

But I got the error and had not success.
It seems that the .xdl64 file is still in use when C4DPL_ENDPLUGINACTIVITY2 make a call. During debugging I just got an error from <filesystem> and "plugin.xdl64" remain the same, which is understandable if the file is still in use:)

So I will try your advice with the storing the data from new one .xdl64 to a Char*. But I have a doubt will it help if the .xdl64 is still busy during C4DPL_ENDPLUGINACTIVITY2.

Thank you again @ferdinand .

Hello @yaya,

what you could try instead is try to rename the plugin files instead of overwriting them. I.e., simply rename myplugin.xdl64 to myplugin.old (or something like that) and then write your new data to myplugin.xdl64 and restart Cinema 4D. In in the startup of Cinema you can then remove all myplugin.old since they are the not in use anymore. This is how our CV-Toolboxd does it. This is however a Python plugin and Python can sometimes be weird with its file access. I only spoke yesterday after I did answer your posting with the author, sorry 🙂

Storing stuff as Char* won't help when you have no file access. I just mentioned this so that you do not rely on some fancy data structure which does not work anymore so deep down in the shutdown process.

Cheers,
Ferdinand

Hi @ferdinand
I've tried your advice with renaming the file. But it is still the same : (
Am I doing it in a way it is intended?
(this func is called when the C4DPL_ENDPLUGINACTIVITY2 occurs )

Bool WriteUpdates()
{ 
	Filename old_fn;	 
	old_fn = GeGetPluginPath();
	old_fn += Filename{ "firstplugin.xdl64" };
	AutoAlloc<BaseFile> file;
	if (file == nullptr)
		return false;
                               // if I change FILEOPEN::READ to WRITE, I get the error 
	if (!file->Open(old_fn, FILEOPEN::READ , FILEDIALOG::ANY, BYTEORDER::V_INTEL, MACTYPE_CINEMA, MACCREATOR_CINEMA))
		return false;

	Filename new_fn;
	new_fn = GeGetPluginPath();
	new_fn += Filename{ "firstplugin.old" };
	file->WriteFilename(new_fn); // here I got an error

        // After that I`ve tried this line (with changing Alloc to Hyperfile), but also got an error
        GeFKill(old_fn, GE_FKILL_FORCE); 
 
	return true;
}

Actually, after a lot of debugging I can see that the C4DPL_ENDPLUGINACTIVITY2 is called before the plugin registration destroyed. Contrary to the statement here: https://developers.maxon.net/docs/Cinema4DCPPSDK/html/group___c4_d_p_l___m_e_s_s_a_g_e_s.html#ga76cde585e6d5b223b10edcb6591a60ac
After C4DPL_ENDPLUGINACTIVITY2 case sends its end
we go to the c4d_pmain.cpp, where c4d starts to free the plugin resources:

C4D_MAIN
{
	static Int32 init_count = 0;

	switch (action)
	{
case C4DPL_END:
			init_count -= 1;
			if (init_count == 0)
			{
				PluginEnd();
				FreeResource();
				DeleteObj(path_storage);
			}
			return 1;

So before that last line it is impossible to replace the plugin file. But after that line we don't go back anymore to our plugins functions where "replace" function exists.

Maybe I am trying to do it entirely in a wrong way? And there is more smart way how others do the update system for their plugins?

From your C++ plugin call Python code to install your new plugin. Python doesn’t care if the file is in use so you can do what you want to it. I personally rename the existing ones then install the new one and when C4D is shut down I delete any renamed files.

@kbar thanks for advice.
Do you rename and delete in your case the .xdl64 file? Or the .pyp/.pypv ?

Hello @yaya,

I think what @kbar meant was that he uses a Python plugin to do the updating/replacing because of the non-strict manner Python does enforce read and write access to files (which was what I meant with "Python being weird with its file access" in may last posting). So, you write a Python plugin which does either download and swap or just swap xdl64 files by renaming them.

What you could also try when you do not want to give up immediately on C++, is to reverse the idea. So you download your plugin, call it myPlugin.new and on startup C4DPL_STARTACTIVITY you could try if you have write access for renaming myPlugin.xdl64.

Cheers,
Ferdinand

This post is deleted!

Oh! Forget guys my message above!

The calling of Python code from C++ plugin in @kbar 's advice does the trick! 🙂

If someone from the future who will read this thread will need the code example, I just use the code snippet from here: https://developers.maxon.net/docs/Cinema4DCPPSDK/html/page_maxonapi_python.html

and feed to it this code:

// call it from somewhere
String code = "import c4d\nimport maxon\nimport os    ";
        code+= "\n\nprint(f'This is simple message')";
        code += "\nos.rename('C:/Code/sdk/plugins/myPlugin/myPlugin.xdl64', 'C:/Code/sdk/plugins/myPlugin/old_myPlugin.old')"; 
        
ExecutePythonScript(code, GetActiveDocument());

😄

This post is deleted!