C Detailed Explanation of Assembly. Unload

  • 2021-12-04 09:48:04
  • OfStack

CLR Product Unit Manager (Unit Manager) Jason Zander 1 article Why isn 't there an Assembly. Unload method? Why there is currently no Assembly. Unload method in CLR that implements functions similar to the UnloadLibrary function in Win32 API.
He thinks that the reason why Assembly. Unload function is implemented is mainly to reclaim space and update version. The former recycles its occupied resources after using Assembly, while the latter unloads the current version and loads the updated version. For example, the dynamic update of Assembly program used in ASP. NET is a good example. However, if the Assembly. Unload function is provided, one problem will arise:

1. Special applications such as GC objects and COM CCW must be traced in order to wrap the code addresses referenced by the code in CLR to be valid. Otherwise, after Unload 1 Assembly, CLR object or COM component will use the code or data address of this Assembly, which will lead to an access exception. However, in order to avoid this kind of wrong tracing, it is currently carried out at AppDomain 1 level. If Assembly. Unload support is to be added, the tracing granularity must be reduced to Assembly 1 level. Although this is not technically impossible, it is too costly.

2. If Assembly. Unload is supported, you must track the handles used by each Assembly's code and references to existing managed code. For example, when JITer compiles methods, the generated codes are all in a unified area. If you want to support unloading Assembly, you must compile each Assembly independently. There are also a number of similar resource usage problems, which are technically feasible but costly to separate tracing, especially on resource-limited systems such as WinCE.

3. CLR supports Assembly loading optimization across AppDomain, that is, domain neutral optimization, so that multiple AppDomain can share one code and speed up loading. Currently, v 1.0 and v 1.1 cannot handle offloading domain neutral type codes. This also makes it difficult to implement the complete semantics of Assembly. Unload.

Based on the above problems, Jason Zander recommends using other design methods to avoid the use of this function. For example, AppDomain and Shadow Copy introduced by Junfeng Zhang on its BLog is the method of solving similar problems by ASP. NET.

When constructing AppDomain, the ShadowCopy policy is enabled by setting AppDomainSetup. ShadowCopyFiles to "true" in AppDomainSetup parameter of AppDomain. CreateDomain method; Then set AppDomainSetup. ShadowCopyDirectories as the replication target directory; Set AppDomainSetup. CachePath + AppDomainSetup. ApplicationName to specify the cache path and file name.
In this way, the semantics of Assembly. Unload can be simulated. In implementation, the Assembly to be managed is loaded into a dynamically established AppDomain, and then its functions are called through transparent proxies across AppDomain, and the semantic simulation of Assembly. Unload is realized by using AppDomain. Unload. chornbe gives a simple wrapper class, and the specific code is shown at the end of the article.

Although this can basically simulate semantically, there are many problems and costs:

1. Performance: In CLR, AppDomain is a logical concept similar to operating system processes, and cross-AppDomain communication is subject to many restrictions just like previous cross-process communication. Although the transparent proxy object can realize the function similar to the cross-process COM object call, and automatically complete the Marshaling operation of parameters, it must pay a considerable price. In the example given by Dejan Jelovic (Cross-AppDomain Calls are Extremely Slow), a call using only built-in types under P4 1.7 G would probably require 1ms. This is too expensive for some functions that need to be called frequently. As he mentioned to implement a drawing plug-in, drawing 200 dots in OnPaint requires a call cost of 200ms. Although it can be optimized through batch calls, the penalty of invocation efficiency across AppDomain is definitely inescapable. Fortunately, it is said that in Whidbey, the built-in types in the call across AppDomain can be optimized without Marshal, so that the call speed is more than 7 times faster than the existing implementation...., I don't know whether to praise the good implementation of Whidbey or lambast the existing version, hehe

2. Ease of use: Types in Assembly that need to be uninstalled separately may not support Marshal, so you need to handle the management of types yourself.

3. Version: How to wrap the correctness of version loading in multiple AppDomain.

In addition, there are security issues. For ordinary Assembly. Load, the loaded Assembly runs under the loader's evidence, which is definitely a potential safety hazard, and may suffer similar attacks like overwriting system files by overflowing programs that read and write files with root privileges under unix. While loading Assembly in one AppDomain alone can set CAS permission alone and reduce execution permission. Because of the 4-level permission control mechanism under CLR architecture, the finest granularity can only reach AppDomain. Fortunately, it is said that Whidbey will add support for loading Assembly with different evidence.

As you can see from these discussions, the semantics of Assembly. Unload are important for programs based on the plug-in model. However, in the current and recent versions, it is a more appropriate choice to simulate its semantics through AppDomain. Although it has to pay for performance and ease of use, it can control factors such as function and security to a greater extent. In the long run, the implementation of Assembly. Unload is completely feasible, and the unloading of classes in Java is the best example. The previous reasons are actually workload and complexity problems, and there are no technical problems that cannot be solved.

# re: AppDomain and Shadow Copy 4/30/2004 2:34 AM chornbe

You must also encapsulate the loaded assembly into another class, which is loaded by the new appdomain. Here's the code as it's working for me: (I've created a few custom exception types, and you'll notice I had them back - they're not descended from MarshalByRefObject so I can't just throw them from the encapsulated code)

--- cut first class file


using System;
using System.Reflection;
using System.Collections;

namespace Loader{

/* contains assembly loader objects, stored in a hash
* and keyed on the .dll file they represent. Each assembly loader
* object can be referenced by the original name/path and is used to
* load objects, returned as type Object. It is up to the calling class
* to cast the object to the necessary type for consumption.
* External interfaces are highly recommended!!
* */
public class ObjectLoader : IDisposable {

// essentially creates a parallel-hash pair setup
// one appDomain per loader
protected Hashtable domains = new Hashtable();
// one loader per assembly DLL
protected Hashtable loaders = new Hashtable();

public ObjectLoader() {/*...*/}

public object GetObject( string dllName, string typeName, object[] constructorParms ){
Loader.AssemblyLoader al = null;
object o = null;
try{
al = (Loader.AssemblyLoader)loaders[ dllName ];
} catch (Exception){}
if( al == null ){
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
AppDomain domain = AppDomain.CreateDomain( dllName, null, setup );
domains.Add( dllName, domain );
object[] parms = { dllName };
// object[] parms = null;
BindingFlags bindings = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;
try{
al = (Loader.AssemblyLoader)domain.CreateInstanceFromAndUnwrap(
"Loader.dll", "Loader.AssemblyLoader", true, bindings, null, parms, null, null, null
);
} catch (Exception){
throw new AssemblyLoadFailureException();
}
if( al != null ){
if( !loaders.ContainsKey( dllName ) ){
loaders.Add( dllName, al );
} else {
throw new AssemblyAlreadyLoadedException();
}
} else {
throw new AssemblyNotLoadedException();
}
}
if( al != null ){
o = al.GetObject( typeName, constructorParms );
if( o != null && o is AssemblyNotLoadedException ){
throw new AssemblyNotLoadedException();
}
if( o == null || o is ObjectLoadFailureException ){
string msg = "Object could not be loaded. Check that type name " + typeName +
" and constructor parameters are correct. Ensure that type name " + typeName +
" exists in the assembly " + dllName + ".";
throw new ObjectLoadFailureException( msg );
}
}
return o;
}

public void Unload( string dllName ){
if( domains.ContainsKey( dllName ) ){
AppDomain domain = (AppDomain)domains[ dllName ];
AppDomain.Unload( domain );
domains.Remove( dllName );
}
}

~ObjectLoader(){
dispose( false );
}

public void Dispose(){
dispose( true );
}

private void dispose( bool disposing ){
if( disposing ){
loaders.Clear();
foreach( object o in domains.Keys ){
string dllName = o.ToString();
Unload( dllName );
}
domains.Clear();
}
}
}

}

--- end cut

--- cut second class file


using System;
using System.Reflection;

namespace Loader {
// container for assembly and exposes a GetObject function
// to create a late-bound object for casting by the consumer
// this class is meant to be contained in a separate appDomain
// controlled by ObjectLoader class to allow for proper encapsulation
// which enables proper shadow-copying functionality.
internal class AssemblyLoader : MarshalByRefObject, IDisposable {

#region class-level declarations
private Assembly a = null;
#endregion

#region constructors and destructors
public AssemblyLoader( string fullPath ){
if( a == null ){
a = Assembly.LoadFrom( fullPath );
}
}
~AssemblyLoader(){
dispose( false );
}

public void Dispose(){
dispose( true );
}

private void dispose( bool disposing ){
if( disposing ){
a = null;
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect( 0 );
}
}
#endregion

#region public functionality
public object GetObject( string typename, object[] ctorParms ){
BindingFlags flags = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;
object o = null;
if( a != null ){
try{
o = a.CreateInstance( typename, true, flags, null, ctorParms, null, null );
} catch (Exception){
o = new ObjectLoadFailureException();
}
} else {
o = new AssemblyNotLoadedException();
}
return o;
}
public object GetObject( string typename ){
return GetObject( typename, null );
}
#endregion

}
}

--- end cut

Some related resources:
Why isn't there an Assembly.Unload method?
http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx

AppDomains ("application domains")
http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx

AppDomain and Shadow Copy
http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx


Related articles: