PDA

View Full Version : .NET: how to unload assembly ?


relief7
01-21-2009, 03:52 AM
Hi !

I was wondering if anyone here has some information on unloading assemblies ? I would like to use some functionality of a .NET dll in MAXScript and when I recompile in VS it obviously cannot overwrite the DLL because it was loaded before via

dotNet.loadAssembly "lib.dll"

and thus is still being used by MAX. Is there a way to unload / release a .NET dll without restarting MAX ? Or is there a certain strategy that .NET developers use for recompiling ?

I really do not want to go the way of creating a stub that dynamically loads my DLL so if anybody had some info on how to comfortably program and test I would be very grateful ! :D

Thanks !
Markus

relief7
01-21-2009, 05:36 AM
Ok, after hours of searching the web I think I am able to answer my own question... :D

I found this amazing thread on the area where Mike Biddlecombe explains how to dynamically compile sources and load them into memory. I modified it so it will read a .cs file and it works like a charm !

http://area.autodesk.com/index.php/forums/viewthread/13286/

No visual studio compile necessary !

LoneRobot
01-21-2009, 08:16 AM
Hi Markus,

Mike's post was really helpful to me too, however I changed the top line to compile Visual Basic instead -

fn CreateAssembly src =
(
/*
code compilation based on an example from Kim David Hauser
click on the link (http://www.codeproject.com/KB/cs/evalcscode.aspx)

altered Mike B's codeprovider to compile visual basic

*/
VBProvider = dotnetobject "Microsoft.VisualBasic.VBCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.GenerateInMemory = true
compilerResults = VBProvider.CompileAssemblyFromSource compilerParams #(src)

if (compilerResults.Errors.Count > 0 ) then
(
errs = stringstream ""
for i = 0 to (compilerResults.Errors.Count-1) do
(
err = compilerResults.Errors.Item[i]
format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line \
err.Column err.ErrorText to:errs
)
MessageBox (errs as string) title: "Errors encountered while compiling VB code"
return undefined
)

a = compilerResults.CompiledAssembly
)

fn StreamToByteArray =
(

vbf = "Imports System.IO\n"
vbf +="Public Class StreamToByteArray\n"
vbf +="Public Function GetStreamAsByteArray(ByVal stream As System.IO.Stream) As Byte()\n"
vbf +="Dim streamLength As Integer = System.Convert.ToInt32(stream.Length)\n"
vbf +="Dim fileData As Byte() = New Byte(streamLength) {}\n"
vbf += "stream.Read(fileData, 0, streamLength)\n"
vbf += "stream.Close()\n"
vbf += "Return fileData\n"
vbf += "End Function\n"
vbf +="End Class"

a = CreateAssembly vbf
a.CreateInstance "StreamToByteArray"
)

global StreamByteArray = StreamToByteArray()

susanta
01-21-2009, 09:15 AM
Yea it is really big problem with .net assembly which can not be unloaded once it loaded. Only AppDomain can be unloaded. Have a look about it on MSDN. If you check softimage .net-integration then you will see, you can dynamically load/unload .net assembly with out any kind of hack. 3dsMax / MaxScript should provide similar kind of functionality by default, so users not have to play with cross AppDomain communication.

Incase of dynamic compilation, which is technically not the replacement of unloading assembly by unloading AppDomain.

drdubosc
01-21-2009, 11:36 AM
... If you check softimage .net-integration then you will see, you can dynamically load/unload a .net assembly with out any kind of hack....


Any idea how SoftImage does this?

susanta
01-21-2009, 12:14 PM
From the use I can assume they have used internally AppDomain unloading but not sure as it is not exposed in user level. Incase of Appdomain, load the assembely in a separate AppDomain and then when need to unload assembly, unload this particular AppDomain instead of a single assembly. But then we also have to take care about cross AppDomain communication. Another hacky way, In every loading call create a separate temp copy of the assembly and load that instead of the original one to avoid dll locking problem. The new loaded definition will replace the old one (but not sure whether old definition get cleaned from memory or not). In case of dynamic compilation to overcome “dll lock” problem either in temp folder or in memory you are basically doing same copy hack.

drdubosc
01-21-2009, 01:05 PM
... In every loading call create a separate temp copy of the assembly and load that instead of the original one to avoid dll locking problem. The new loaded definition will replace the old one

I've not managed to get this roll-your-own shadow copying to work. On the second call of loadAssembly, the original version stubbornly remains in memory? Maybe I'm doing something else wrong.

ZeBoxx2
01-21-2009, 01:29 PM
I've not managed to get this roll-your-own shadow copying to work. On the second call of loadAssembly, the original version stubbornly remains in memory? Maybe I'm doing something else wrong.
I think what he meant was that you should start out (after a fresh max start, say) by copying the DLL to a (temp) location and loadAssembly that file - to prevent a lock on the original file (so you can, say, overwrite it with a new compile). After that, the next time you want to load the assembly, again create a copy and loadAssembly that new copy.

Now...
- the original file that you just recompiled remains unlocked
- your first copy is still locked, but its definition in memory overwritten by the 2nd copy
- your second copy is locked

You'd end up with quite a few 'shadow copies', but then.. loadAssembly should be used sparingly - I suppose you'd only really run into this situation when developing an assembly.

drdubosc
01-21-2009, 02:07 PM
Ok, thanks, ZB. It's klunkier than I thought. But perhaps OK for development.

EDIT: Thanks, too, susanta ... this one will send me back to my books!

susanta
01-21-2009, 02:21 PM
ZeBoxx2, yea that is easy way to overcome this problem...
Sorry, the hard way I have actually not dicussed the process in deatail in my previous post.
Here is the way to load multiple versions of the assembly (diffrent version number)

http://infosysblogs.com/microsoft/2007/04/loading_multiple_versions_of_s.html

if you need to autoincrement during development quickly in visual studio here it is:
http://www.codeplex.com/autobuildversion

LoneRobot
01-21-2009, 08:16 PM
i guess currently you have to get used to restarting max when testing a custom assembly. :hmm:

I copy everything from Visual Studio to a launch directory, still have to restart. boo.

susanta
01-22-2009, 01:20 PM
Yea, It is bit wired...please try the following way, I have used Maxscript to make it bit easy to understand for MaxScripters as my level best.

---here I have one visual studio C# class lib project with this code
---what I'm compiling as MYLib.dll

/* using System;
* using System.Windows.Forms;
* namespace MYLib
* {
*
* /// <summary>
* /// First version
* /// </summary>
* //public class MyClass
* //{
* // public void Call()
* // {
* // MessageBox.Show("Say First");
* // }
* //}
*
* /// <summary>
* /// Second version
* /// </summary>
* public class MyClass
* {
* public void Call()
* {
* MessageBox.Show("Say Second");
* }
* }
* }
*/

------------Now in Maxscript----------------------------

---Create dotnet class to use some useful static functions
AppDomain = dotNetClass "System.AppDomain"
Assembly = dotNetClass "System.Reflection.Assembly"
AssemblyName = dotNetClass "System.Reflection.AssemblyName"

---set the shadow copy so actually original assembly file can be overriten...SetShadowCopyFiles is obsoluted according to MSDN
---but that is the only way to enable ShadowCopy option for current AppDomain. Because through Maxscript
---creation of new appdomain is not possible so we have to use current AppDoamin and for 3dsMax2009 ShadowCopy for current Appdomain is false bydefault
--- whatever the dir we use with SetShadowCopyPath, all assembly loaded later from this directory automatically will be shadow copied by .net runtime
---I'm for demonstartion using "C:\Release" but you can use directly use yourVCSharpProject/Bin/Release or Debug folder
---make sure everytime you have differnt version number, In case of Visual Studio 2008 C# project version settings
---under project settings -> Application-->Assembly Information or you can change AssemblyInfo.cs file of your project manually
----or the addin I have specified for visual studio to autoincrement it you can use http://www.codeplex.com/autobuildversion

---using default cache path ensure delete of shadow copy files will be handle by runtime automatically
---if you use your own cache path using AppDomain.CurrentDomain.SetCachePath then you have delete them manually
---So I'm not using that

AppDomain.CurrentDomain.SetShadowCopyPath (@"C:\Release\")
AppDomain.CurrentDomain.SetShadowCopyFiles()


----load the first version...again don't forget to use different version for every compile in visual studio
----AssemblyName.GetAssemblyName using this method we can query to the dll what the version number we have setted in visual studio
assemName = AssemblyName.GetAssemblyName(@"C:\Release\MYLib1.dll")
print assemName.FullName
---result "MYLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d8101bd49700bbd5"

----Now load it using assembly name, don't use LoadFromFile
assem = Assembly.Load assemName
print assem.Location
---will print the temp file path runtime used for shadow copy somthing like "\Application Data\assembly\dl3\OAV6J36X.Q9J\1Z7DWQ44.552\0f496cae\004a3872_967cc901\MYLib1.dll"

---use this way to create object of sepcific version instead of (dotNetObject "MYLib.MyClass")
myObj = assem.CreateInstance "MYLib.MyClass" ---create object of first version of MYLib.MyClass
myObj.Call() --- call the first version of the method

assemName = AssemblyName.GetAssemblyName(@"C:\Release\MYLib2.dll")
print assemName.FullName
---result "MYLib, Version=1.1.0.0, Culture=neutral, PublicKeyToken=d8101bd49700bbd5"

assem = Assembly.Load assemName
print assem.Location
--- show the temp file path runtime used for shadow copy somthing like "\Application Data\assembly\dl3\OAV6J36X.Q9J\1Z7DWQ44.552\3da67b63\00d97aa5_967cc901\MYLib2.dll"

myObj = assem.CreateInstance "MYLib.MyClass" ---create object of second version of MYLib.MyClass
myObj.Call()--- call the second version of the method

drdubosc
01-22-2009, 02:07 PM
Thankyou for your trouble describing this, susanta. I hadn't understood the documentation, and was trying to load the wrong way ( giving no access to the version No. ). This has saved a pile of trial and error, discovering the behaviour of GetAssemblyName, etc. :)

LoneRobot
01-28-2009, 09:05 AM
here's a thing - can you dynamically compile dlls that have referenced assemblies? this has worked fine with anything within the system namespace, but when i have tried adding code containing things like System.drawing.image im getting errors. In VS this is done for you by adding the assembly as a reference and then putting imports or using before the class definition. However i wasnt sure how to do this manually.

drdubosc
01-28-2009, 10:48 AM
here's a thing - can you dynamically compile dlls that have referenced assemblies?


Hi, LR ... I would have thought you have to set (.Add to) the ReferencedAssemblies (http://msdn.microsoft.com/en-us/library/system.codedom.compiler.compilerparameters.referencedassemblies.aspx) property of CompilerParameters, to kick the linker into action before the compile starts?

LoneRobot
01-28-2009, 11:05 AM
Hey Dr,

ah, Thanks for that, I'll look into it and report back. cheers!

CGTalk Moderation
01-28-2009, 11:05 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.