Test plugin written on C# without reloading 3d MAX


The ephere MaxDotNet Utility plugin no longer exists. All of the values that were exposed in the Utility plugin are stored in the .config file I outlined.



Well everything going good. But if i open the plugin then close it and trying reopen it gives the error “can`t access to disposed object”. Could anyone give advice how to avoid it.


Did anyone try to recompile this plugin unloader by Alex Budovski for versions above 2010? maxplugins.de
I experience same exact compile unresolved errors LNK2019 as somebody noted here

Any chance to get it working for newer versions?
Or maybe there’re another methods available to develop c++ maxscript exposed functions without reloads?

I also found another decade old thread in russian where is seems like possible.


for all versions after MAX 8 (as I remember) you can’t unload DLL … it was not safe for most DLLs, and probably MAX developers decided to completely eliminate this possibility.

so, there is no way to unload c++ SDK DLLs

but depending on how you wrote your plugin you can override the old one (I hope). You can do it for example with the most of .NET DLLs


Thank you, Denis.
My cpp skills are below novice so I won’t be able to override anything in next few years (optimistically) :slight_smile:
Reloading c# dll isn’t a problem. I was hoping that everybody just forgot about that possibility in c++ sdk, but no luck.
Need to ask Santa for a brand new ssd


Upto and including 2010 it is possible to unload a dll (except mxs dlx’s). It’s a convoluted task and not for the faint hearted requiring some custom tools for checking whether max is running and what version is running from visual studio. Then the tool (i use python with a custom extension) uses max’s com interface (peeking/hacking the resource entry as it goes) to save the current file, reset max, unload the dll (requires it’s own custom dlx!). you can now copy over the new dll, and reload it finally reload the saved file. Simples :slight_smile: Since 2010 version they no longer export/make public the key function that lets you unload the dll. So I do all my primary dev in 2010.

here’s some of the python post build script i use (WinProcess is a custom c extension to python)


#file:     UpdateMaxPluginEx.py
#purpose:  manage visual studio post build event for max plugins 
#last ed:  
#usage:    UpdateMaxPluginEx.py [-p/--filepath <pluginname>] [-v/--version <int>] [-x/--x64] [-r/--restartmax] [-q/--quitmax]

#python ..\..\..\scripts\UpdateMaxPluginEx.py -p $(TargetPath)   -v 17 -q --x64

import os;
import shutil;
import time;
import win32api;
#our custom extension
import WinProcess;
import win32com.client;
import sys;
import getopt;
import os.path;

from MaxOLE import *;

# initialize stuff

supportedversions = [11,12,13,14,15,16,17];
max_functions = ["OLECommand","OLESaveMaxFile","OLELoadLastSavedFile","OLEResetMax","OLEQuitMax","OLEUnloadPlugin","OLELoadPlugin"];
mvStrToInt = {"3ds Max 2009":11,"3ds Max 2010":12,"3ds Max 2011":13,"3ds Max 2012":14,"3ds Max 2013":15,"3ds Max 2014":16,"3ds Max 2015":17};
mvIntToStr = {11:"2009",12:"2010",13:"2011",14:"2012",15:"2013",16:"2014",17:"2015"};
sleeptime = 3;
args = sys.argv;
version = 0;
archi = "x86";
restartMax = False;
quitMax = False;

MaxAppName = "3dsmax.exe";
MaxApp = "MAX.Application";
MaxAppKeys = CollectMaxAppKeys(MaxApp);
usageStr = "UpdateMaxPluginEx.py [-p/--filepath <pluginname>]  [-v/--version <int>] [-x/--x64] [-r/--restartmax] [-q/--quitmax]";

# Parse arguments, shouldn't really fuck up as it has very controlled usage
# but hey ho I may forget, I mean I will forget ! 

    options, remainder = getopt.gnu_getopt(sys.argv[1:], 'p:v:xrq', ['filepath=', 'version','x64','restartmax','quitmax']);
except getopt.GetoptError, err:
        # print help information and exit:
        print str(err); 
        print usageStr;

# parse the args

for opt, arg in options:
    if opt in ('-p', '--filepath'): 
        sourcefile = arg;
    elif opt in ('-v', '--version'):
        version = int(arg);
    elif opt in ('-x', '--x64'):
        archi = "x64";    
    elif opt in ('-r', '--restartmax'):
        restartMax = True;
    elif opt in ('-q', '--quitmax'):
        quitMax = True;

if not version in supportedversions:
    print "Script Error: Unsupported version, exiting UpdateMaxPluginEx.py"

print ("**** 3ds Max "+ mvIntToStr[version] + " " + archi + " ****");

pfiles = os.environ["ProgramFiles(x86)"];
if archi == "x64":
    pfiles = os.environ["ProgramFiles"];

# get the short plugin name generate the destination file

pluginName = os.path.basename(sourcefile);
destfile = pfiles + "\\Autodesk\\3ds Max " + mvIntToStr[version] + "\\plugins\\" + pluginName;
FullMaxAppName = pfiles + "\\Autodesk\\3ds Max " + mvIntToStr[version] + "\\3dsmax.exe"

print ("Running..." + os.path.basename(args[0]));
print ("Destination File... " + destfile);

#get current max that is running see if it's ours

print FullMaxAppName;
max_is_running = False;
winprocess_res = WinProcess.IsRunningEx(FullMaxAppName);
if winprocess_res == 1:
    max_is_running = True;
    #print max_app_path;
    #max_app_path_list = max_app_path.split(os.sep);
    #progfiles = max_app_path_list[0] + os.sep + max_app_path_list[1];

    #max_app_archi = "x86";
    #if progfiles == os.environ["ProgramFiles"]:
     #   max_app_archi = "x64";

    #max_app_version = mvStrToInt[max_app_path_list[3]];
   # print max_app_version;
   # print max_app_archi;
    #if max_app_version == version and max_app_archi == archi:
     #   max_is_running = True;

# our max is not running do the copy

if not max_is_running:
    print (FullMaxAppName  + " Is Not running on this System");
    print "Copying file...\n" +  sourcefile + "\nto...\n" + destfile + "\n"; 
    shutil.copy(sourcefile, destfile);
    if restartMax:
        print "starting 3dsmax.exe";
        if getOLESupport(MaxAppKeys, version):
            win32com.client.Dispatch(MaxApp + '.' + str(version));
            print "Unable to start Max as Specified version Not OLE supported";
    print "UpdateMaxPlugin.py complete";         

# ok max is running make sure it's the current OLE

#if setCurrentMaxAppVersion(MaxAppKeys, version):
#    setCurrentMaxAppArchitexture(MaxAppKeys, archi);
#   print "Script Error: Unable to Interact with Max as Specified version Not OLE supported";
#    sys.exit();

keyindex = getOLESupport(MaxAppKeys, version)
if keyindex != -1:
    MaxApp += ".";
    MaxApp += str(version); 
    setOLELocalServer32(MaxAppKeys, keyindex, MaxApp);
    print "Dispatching to... " + MaxApp;
    print "Script Error: Unable to Interact with Max as Specified version Not OLE supported";

# ok should be able to talk to max now

conn = win32com.client.Dispatch(MaxApp);
for funcs in max_functions: # add our OLE functions

# save the current file

print "Saving Max file";
res = conn.OLESaveMaxFile();

if quitMax: # quit max option used for dlx plugins as they can't be unloaded and versions after 2010
    print "Exiting Max";
        res = conn.OLEQuitMax();
    time.sleep(sleeptime);    #give max time to shut down
    print "Copying file...\n" +  sourcefile + "\nto...\n" + destfile + "\n"; 
    shutil.copy(sourcefile, destfile);
    if restartMax:   # only restart if we actually have quit max
        conn = win32com.client.Dispatch(MaxApp);
        #conn.OLELoadLastSavedFile(); #
else: # we risk it for a swisskit 
    print "Reseting Max";
        res = conn.OLEResetMax();
    print "Unloading Plugin"; 
    print "Copying file...\n" +  sourcefile + "\nto...\n" + destfile + "\n"; 
    print "Loading Plugin";
    print "Loading Saved File"; 

print "UpdateMaxPluginEx.py complete";

i made a gif to show the benefit to my work flow…

[SDK] How do you test your plugins? Restart 3ds every time?

wow! looks very cool… maybe too cool for me

it’s very far from i do. my business is c++ dlx and dlm :slight_smile:


I’ve managed to enable unloading in versions after 2010…It’s a bit of a hack…

struct dlldesc_struct
	int*	pad[5];	
	bool	loaded;


		HINSTANCE hinst = plgDesc->GetHandle();
		FreeLibrary(hinst); // use the window function that max would use

// and hack the memory so max knows it's unloaded

		DllDesc *plgDesc_temp = const_cast<DllDesc*>(plgDesc); // cast away the const
		dlldesc_struct** dll_desc_strut = reinterpret_cast<dlldesc_struct**>(plgDesc_temp);
		(*dll_desc_strut)->loaded = false;

// this would be the 2010 way of doing it (when the functions were public