[SDK] [c#] Iterating on a DLL using Visual Studio ('unload'/'reload' Assemblies)


#1

What’s the best approach for iterative C# DLL development with Visual Studio and 3DS Max?

I’m using MS Visual C# Express to write C# class libraries
these .DLLs are load in Max Script using dotNet.LoadAssembly

But iteration is a b*tch because I can’t “unload” an assembly : I have to restart max.

Is there has to be a more efficient way to update my DLLs in Max after I recompile them.

[edit] solution found

[UPDATED] added commands to instantiate a class as an object , or call a function in a static class, optionally passing parameters as mxs arrays in either case.
[UPDATED] links fixed.

The dllManager.ms script is posted on my own code page for easier maintenance.


#2

There’s no way to unload an assembly, unless you create it in a new AppDomain, which is usually counterproductive.

What you could do though is keep reloading the same assembly, by loading it from memory bytes, and not directly from file.

assembly = (dotnetClass "System.Reflection.Assembly").Load ((dotnetClass "System.IO.File").ReadAllBytes @"C:\myassembly.dll")

The downsides are you can no longer use direct namespace resolving, once a namespace is resolved it will not get updated and always refer to the first loaded assembly. For example if you use:

dotnetObject "MyAssembly.MyClass"

It will always refer to the first loaded assembly.

Instead you’d have to use:

assembly.createInstance "MyAssembly.MyClass"

where “assembly” is the value returned from the loading code.


C# and Maxscript -- Communication
#3

Nice trick, lo! :wink:


#4

Yeah, really nice one Lo! :thumbsup:


#5

Thanks you for your response.

It certainly is a nice trick.
I was adapting the apporoach in post #12 of this thread

That approach’s use of SetShadowCopyPath & SetShadowCopyFiles is an obscure black box to me, but your approach seems more direct, and skips the need for explicit Assembly versioning.


#6

I tested and rolled it into a simple MXS tool for easy DLL reloading in Max:
[UPDATED] added ability to instantiate a Class with parmaters.

I moved my dllManager.ms script to my own code page for easier maintenance.


#7

Looks cool.
Two other useful methods that you can add are:

  1. Create instance with parameters.
  2. Invoke a method or property on a static class.

#8

looks like child’s play when compared to what’s required for c++ [sdk] development plugin unloader… :argh:


#9

I’m working on #1 right now. from the Assembly.CreateInstance Method documentation,It appears I should be able to pass a System.Object[] containg an array of arguments.
But I’m not sure how to get convert a MXS array of arguments to a System.Object[] of arguments.


#10

1.) Create instance with parameters.
is Harder than I thought, at least if I must avoid the use of dotNetObject when initiating.
My C# test class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyNameSpace
{
    public class MyClassWithArg
    {
        public string arg;
        public MyClassWithArg(string thisArg)
        {
            arg = thisArg;
        }
        public string sayArg()
        {
            return ("MyClassWithArg.sayArg has been called.
	MyClassWithArg.arg = " + arg + ".
");
        }
    }
}

It’s easy enough to call with an argument using this MXS

MyClassWithArgs=dotNetObject "MyNameSpace.MyClassWithArg" "hello, world" 

but this gives an error


MyClassWithArgs=dllManager.assembly.CreateInstance "MyNameSpace.MyClassWithArg"  "hello,world"
-- Runtime error: No method found which matched argument list

as does converting the argument into a System.Object[] dotnet value:


dotNetArg=dotNet.ValueToDotNetObject #("hello,world") (dotNetObject "System.Object")
MyClassWithArgs=dllManager.assembly.CreateInstance "MyNameSpace.MyClassWithArg"  dotNetArg
-- Runtime error: No method found which matched argument list

It seems to be an issue in properly passing the arguments to the constructor.
Anyone have insight?


#11

Stack Overflow to the rescue.

Looks like System.Assembly.CreateInstance cannot actually create an instance AND pass and argument to the constructor,
but System.Activator.CreateInstance can do it. The argument(s) still need to be converted into dotNet objects before p[assign from MXS.]

Msx code

activator = dotNetClass "System.Activator"
dotNetType=dllManager.assembly.GetType("MyNameSpace.MyClassWithArg")
dotNetArg=dotNet.ValueToDotNetObject #("hello,world") (dotNetObject "System.Object")
MyClassWithArgs=activator.CreateInstance dotNetType dotNetArg
--dotNetObject:MyNameSpace.MyClassWithArg
MyClassWithArgs.arg
--"hello,world"

I’ll roll this into the code above
Though, I admit to being fairly over my head with C#, I do not know if there is any risk to creating all these DotNetClass objects just for one method use.


#12

assembly.createInstance can also do it, though the signature is more tedious:

http://msdn.microsoft.com/en-us/library/ck6xe688(v=vs.110).aspx


#13
public virtual Object CreateInstance(
	string typeName,
	bool ignoreCase,
	BindingFlags bindingAttr,
	Binder binder,
	Object[] args,
	CultureInfo culture,
	Object[] activationAttributes
)

I saw that and all those weird arguments scared me away.
No idea what to supply for Binder, BindingFLags, or CultureInfo , activationAttributes

Any hint for #2? My test static class won’t instantiate via CreatInstance
I assume It’s because Static Classes aren’t meant to instantiate :slight_smile:


#14
((assembly.GetType "YourType").GetMethod "YourMethod").Invoke undefined undefined

#15

got the static class working, with and without args.
thanks for your help, lo!


#16

Hi,

I’ve been using Logan’s script to load dll’s while developing them in VS. Works great. However, now I’m using a 3rd party dll in my own dll (json.net in my case). While in visual studio I add the reference and use a test project to run the methods in my dll. This works nice. But when I use the dll manager to load my dll into my maxscript and run my methods I get an error which tells me the json.net assembly can’t be found.
I’ve also used the methods described in this thread to load both dll’s before running methods from my dll, to no avail.
I guess that loading both dll’s into memory doesn’t make them aware of each other. Is there a way to do this while still being able to unload them, or reload new ones while developing?
By the way: using loadassembly on both dll’s works fine, but takes away some of the flexibility.


#17

glad you found it useful.
I have also bumped into some difficulties loading a dll from VS when it uses json.net as a resource with my tool. I am unsure if the problem extends to other added resource dlls.

I have yet to identify the nature of the problem. The dll works fine when used by another C# VS test program, so I think there is an unknown in Max’s implementation of C# that I am stumbling against. Can you post a screen grab of the error? I probaly won’t be abel to fix it anytime soon, but I can at least verify it’s the same issue.


#18

Hi Logan,

this is the error I’m getting when loading both the json.net and my assembly and calling a method in my assembly.

-- Runtime error: dotNet runtime exception: Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified.
 

I’m using this to load both assemblies

json_assembly = (dotnetClass "System.Reflection.assembly").Load ((dotnetClass "System.IO.File").ReadAllBytes ("some/path/Newtonsoft.Json.dll"))
 json_assembly.createInstance "Newtonsoft.Json"
 

When I use the ordinary dotnet.loadassembly on both assemblies everything works fine.

This issue is not unique to json.net by the way. I’ve created two separate assemblies myself where one is calling methods from the other. When trying to load them with the above methods, I get exactly the same type of error.


#19

To answer my own question I’ve finally found out how to use multiple referenced assemblies in 3dsMax without locking them. It’s a rather lengthy process so I’ve written an article about it: http://www.klaasnienhuis.nl/2015/12/referenced-dlls-without-locking-in-3dsmax/

In short: you need to embed the extra dll’s in your own dll and add an eventhandler which loads the extra dll’s when they’re not found by default.