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
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 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
#started:
#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 !
try:
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;
sys.exit(2);
# 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"
sys.exit();
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));
else:
print "Unable to start Max as Specified version Not OLE supported";
print "UpdateMaxPlugin.py complete";
sys.exit();
# ok max is running make sure it's the current OLE
#if setCurrentMaxAppVersion(MaxAppKeys, version):
# setCurrentMaxAppArchitexture(MaxAppKeys, archi);
#else:
# 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;
else:
print "Script Error: Unable to Interact with Max as Specified version Not OLE supported";
sys.exit();
# ok should be able to talk to max now
conn = win32com.client.Dispatch(MaxApp);
for funcs in max_functions: # add our OLE functions
conn._FlagAsMethod(funcs);
# 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";
try:
res = conn.OLEQuitMax();
except:
res;
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";
try:
res = conn.OLEResetMax();
except:
res;
print "Unloading Plugin";
conn.OLEUnloadPlugin(pluginName);
print "Copying file...\n" + sourcefile + "\nto...\n" + destfile + "\n";
shutil.copy(sourcefile,destfile);
print "Loading Plugin";
conn.OLELoadPlugin(pluginName);
print "Loading Saved File";
conn.OLELoadLastSavedFile();
print "UpdateMaxPluginEx.py complete";
i made a gif to show the benefit to my work flow…
Thank you, Denis.
My cpp skills are below novice so I won’t be able to override anything in next few years (optimistically)
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
wow! looks very cool… maybe too cool for me
it’s very far from i do. my business is c++ dlx and dlm
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;
};
#if MAX_VERSION_MAJOR > 13
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;
#else
// this would be the 2010 way of doing it (when the functions were public
If I unload and then try to reload the dll without recompile it shows me the dialog saying that such ClassID is already registered. Max crashes after a while.
If I unload and then try to reload recompiled dll it shows me the dialog saying that dll compile dates are mismatching and then after a while max crashes.
that’s the code I use to load and unload
LoadDllsFromDir (getdir #maxroot + "plugins\\") "MaterialID*.dlm" -- loading
unloaddll() -- unloading
dlx
struct dlldesc_struct
{
int* pad[5];
bool loaded;
};
def_visible_primitive(unloaddll, "unloaddll");
Value* unloaddll_cf(Value** arg_list, int count)
{
DllDir* dd = (GetCOREInterface7())->GetDllDirectory();
MaxSDK::Util::Path dllpath;
dllpath.SetPath(_T("hard-coded-path-to-my-dlm-file"));
if (!dd)
{
mprintf(_T("DllDir is NULL\n"));
return &false_value;
}
int index = dd->FindDllDescFromDllPath(dllpath);
mprintf(_T("Dll index is %d\n"), index);
if ( index == -1 ) return &false_value;
const DllDesc* desc = &dd->GetDllDescription(index);
mprintf(_T("Unloading mod: %s\n"), desc->Description());
HINSTANCE hinst = desc->GetHandle();
FreeLibrary(hinst);
DllDesc *plgDesc_temp = const_cast<DllDesc*>(desc); // cast away the const
dlldesc_struct** dll_desc_strut = reinterpret_cast<dlldesc_struct**>(plgDesc_temp);
(*dll_desc_strut)->loaded = false;
return &true_value;
}
I’ve tried to set CanAutoDefer to false but it didn’t change anything.
What else should I do to make it work in 2014 ?
I hope that I can do better than just first two steps
oh thats not nearly enough !
struct dlldesc_struct
{
int* pad[5];
bool loaded;
};
def_visible_primitive(UnloadPlugin, "UnloadPlugin");
// UnloadPlugin "pgModifiers.dlm"
Value* UnloadPlugin_cf(Value **arg_list, int count)
{
check_arg_count(UnloadPlugin, 1, count);
const TCHAR* name = arg_list[0]->to_string();
DllDir *dd = MAXScript_interface->GetDllDirectory();
DllDesc *plgDesc = GetDescFromPlugname(name, MAXScript_interface);
dllerror = 0;
if(!plgDesc)
{
dllerror = kdll_notfound;
mprintf(_T("kdll_notfound\n"));
return &false_value;
}
#if MAX_VERSION_MAJOR > 13
if(!plgDesc->IsLoaded())
#else
if(!plgDesc->loaded)
#endif
{
dllerror = kdll_unloaded;
mprintf(_T("kdll_unloaded\n"));
return &false_value;
}
int na = 0;
for(int i = 0; i < plgDesc->NumberOfClasses(); ++i)
{
ClassDesc* classdesc = (*plgDesc)[i];
if(!classdesc)
continue;
ClassEntry* classentry = dd->ClassDir().FindClassEntry(classdesc->SuperClassID(),classdesc->ClassID());
if(classentry)
na += classentry->UseCount();
}
if(na == 0)
{
int noc = plgDesc->NumberOfClasses();
int unloadCount = 0;
for(int i = 0 ;i < noc; ++i)
{
ClassDesc* classdesc = (*plgDesc)[i];
if (!classdesc)
continue;
unloadCount += (MAXScript_interface->DeleteClass(classdesc));
}
if(unloadCount != noc)
{
dllerror = kdll_cannot_unload;
mprintf(_T("kdll_cannot_unload (unloadCount == noc)\n"));
return &false_value;
}
#if MAX_VERSION_MAJOR > 13
HINSTANCE hinst = plgDesc->GetHandle();
FreeLibrary(hinst);
//DllDesc *plgDesc_temp = const_cast<DllDesc*>(plgDesc);
dlldesc_struct** dll_desc_strut = reinterpret_cast<dlldesc_struct**>(plgDesc);
(*dll_desc_strut)->loaded = false;
#else
TCHAR out[MAX_PATH];
GetModuleFileName(plgDesc->handle, out, MAX_PATH-1);
TCHAR path[MAX_PATH];
BMMSplitFilename(out, path, NULL, NULL);
#ifdef _UNICODE
plgDesc->directory.printf(L"%s",path);
#else
plgDesc->directory.printf("%s",path);
#endif
plgDesc->tDescription = plgDesc->Description();
plgDesc->Free();
plgDesc->loaded = FALSE;
#endif
}
else
{
dllerror = kdll_cannot_unload;
mprintf(_T("kdll_cannot_unload (na != 0)\n"));
return &false_value;
}
return &true_value;
}
Thanks, Klvnk.
I had to make it the way it was intially otherwise it refuses to compile.
DllDesc *plgDesc_temp = const_cast<DllDesc*>(plgDesc);
dlldesc_struct** dll_desc_strut = reinterpret_cast<dlldesc_struct**>(plgDesc_temp);
(*dll_desc_strut)->loaded = false;
If I try to reload without recompile it works just fine now. No window shows up.
But after recompile this Deferred Dll error still keeps showing up…