C# about assembly Unload details

Time:2022-7-22

CLR product unit managerJason ZanderAn article a few days agoWhy isn’t there an Assembly.Unload method?It explains why there is no assembly in CLR that implements the function similar to the unloadlibrary function in Win32 API Unload method.
He believes that the reason for realizing assembly The unload function is mainly used to recover space and update the version. The former recycles the resources occupied by the assembly after using it, and the latter unloads the current version and loads the updated version. For example, asp Net is a good example. But if assembly The unload function causes some problems:

1. In order to package the code addresses referenced by the code in the CLR are valid, special applications such as GC objects and com CCW must be tracked. Otherwise, after unloading an assembly, the code or data address of the assembly will be used by CLR objects or COM components, which will lead to access exceptions. In order to avoid this kind of error, the tracking is currently carried out at the AppDomain level. If you want to add assembly If unload is supported, the granularity of tracking must be reduced to the assembly level. Although this is not technically impossible, it is too expensive.

2. If assembly is supported Unload must track the handle used by the code of each assembly and the reference to the existing managed code. For example, when jiter compiles methods, the generated code is in a unified area. If you want to support uninstalling assemblies, you must compile each assembly independently. In addition, there are some similar resource usage problems. Although it is technically feasible to separate and track, it is expensive, especially in systems with limited resources such as wince.

3. CLR supports the optimization of assembly loading across AppDomains, that is, the optimization of domain neutral, so that multiple AppDomains can share a code to speed up loading. At present, v1.0 and v1.1 cannot handle unloading domain neutral type code. This also leads to the implementation of assembly The difficulty of unloading complete semantics.

Based on the above problemsJason ZanderIt is recommended to use other design methods to avoid the use of this function. Such asJunfeng ZhangIntroduced on its blogAppDomain and Shadow Copy, which is asp Net to solve similar problems.

When constructing AppDomain, use appdomain Appdomainsetup Set shadowcopyfiles to “true” to enable the shadowcopy policy; Then set appdomainsetup Shadowcopydirectories is the replication target directory; Set appdomainsetup CachePath + AppDomainSetup. ApplicationName specifies the cache path and file name.
In this way, assembly can be simulated Semantics of unload. The implementation is to load the assembly that needs to be managed into a dynamically established AppDomain, and then call its functions through a transparent proxy across the AppDomain, using the AppDomain Unload implements assembly Simulation of unload semantics.chornbeA simple wrapper class is given, and the specific code is shown at the end of the article.

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

    1. Performance: in CLR, AppDomain is a logical concept similar to the process of operating system. Cross AppDomain communication is subject to many restrictions as before. Although the transparent proxy object can realize the function similar to the cross process COM object call and automatically complete the marshalling operation of parameters, it must pay a considerable price.Dejan JelovicExamples given(Cross-AppDomain Calls are Extremely Slow)In P4 1.7g, it takes about 1ms to use only built-in calls. This is too expensive for some functions that need to be called frequently. As he mentioned, it takes 200ms to draw 200 points in onpaint to implement a drawing plug-in. Although it can be optimized through batch calls, the penalty of cross AppDomain call efficiency is certainly inescapable. Fortunately, it is said that in Whidbey, the built-in types in cross AppDomain calls can be optimized without Marshall, so that the call speed is more than 7 times faster than that of the existing implementation, I don’t know whether to praise Whidbey’s good implementation or scold the existing version for its rottenness, hehe

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

3. Version: how to package the correctness of version loading in multiple AppDomains.

In addition, there are security issues. For ordinary assemblies In terms of load, the loaded assembly runs under the loader’s evidence, which is definitely a security risk. It may suffer from similar attacks under UNIX, such as overwriting system files by overflowing programs that read and write files with root privileges. Loading an assembly in an AppDomain alone can set CAS permissions and reduce execution permissions. Because of the four level permission control mechanism under the CLR architecture, the smallest granularity can only reach AppDomain. Fortunately, it is said that Whidbey will add support for loading assemblies with different instances.

Through these discussions, we can see that assembly For programs based on plug-in model, the existence of its semantics is very important. However, at present and in recent versions, it is a more appropriate choice to simulate its semantics through AppDomain. Although performance and ease of use need to be paid, it can control functions, security and other factors to a greater extent. In the long run, assembly The implementation of unload is completely feasible, and the unloading of classes in Java is the best example. The above reasons are actually problems of workload and complexity, and there are no unsolvable technical problems.

# 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

This article is about c35; about assembly That’s all for the detailed article of unload. More related c# about assembly Please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!