通过应用程序域AppDomain加载和卸载程序集[zz]

[Unity][ILRuntime][C#]从零开始接入热更新 参考资料: 1.ILRuntime Unity热更新 2.从零开始 — ILRuntime (ourpalm.github.io) 3.unity python热更新_Unity C#热更新方案 ILRuntime学习笔记(一) Hello World 4. 阅读详情

微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外。车门的钥匙只有一把,若要获得还需要你费一些心思。我在学习Remoting的时候,就遇到一个扰人的问题,就是Remoting为远程对象仅提供Register的方法,如果你要注销时,只有另辟蹊径。细心的开发员,会发现Visual Studio.Net中的反射机制,同样面临这个问题。你可以找遍MSDN的所有文档,在Assembly类中,你永远只能看到Load方法,却无法寻觅到Unload的踪迹。难道我们装载了程序集后,就不能再将它卸载下来吗?

想一想这样一个场景。你通过反射动态加载了一个dll文件,如今你需要在未关闭程序的情况下,删除或覆盖该文件,那么结果会怎样?很遗憾,系统会提示你无法访问该文件。事实上该文件正处于被调用的状态,此时要对该文件进行修改,就会出现争用的情况。

显然,为程序集提供卸载功能是很有必要的,但为什么微软在其产品中不提供该功能呢?CLR 产品单元经理(Unit Manager) Jason Zander 在文章 Why isn't there an Assembly.Unload method? 中解释了没有实现该功能的原因。Flier_Lu在其博客里(Assembly.Unload)有详细的中文介绍。文中介绍了解决卸载程序集的折中方法。Eric Gunnerson在文章《AppDomain 和动态加载》中也提到:Assembly.Load() 通常运行良好,但程序集无法独立卸载(只有 AppDomain 可以卸载)。Enrico Sabbadin 在文章《Unload Assemblies From an Application Domain》也有相关VB.Net实现该功能的相关说明。

尤其是Flier_Lu的博客里已经有了很详细的代码。不过,这些代码没有详细地说明。我在我的项目中也需要这一项功能。这段代码给了我很大的提示。但在实际的实现中,还是遇到一些具体的问题。所以我还是想再谈谈我的体会。

通过AppDomain来实现程序集的卸载,这个思路是非常清晰的。由于在程序设计中,非特殊的需要,我们都是运行在同一个应用程序域中。由于程序集的卸载存在上述的缺陷,我们必须要关闭应用程序域,方可卸载已经装载的程序集。然而主程序域是不能关闭的,因此唯一的办法就是在主程序域中建立一个子程序域,通过它来专门实现程序集的装载。一旦要卸载这些程序集,就只需要卸载该子程序域就可以了,它并不影响主程序域的执行。

不过现在看来,最主要的问题不是子程序域如何创建,关键是我们必须实现一种机制,来达到两个程序域之间完成通讯的功能。如果大家熟悉Remoting,就会想到这个问题不是和Remoting的机制有几分相似之处吗?那么答案就可以呼之欲出了,对了,就是使用代理的方法!不过与Remoting不同的是两个程序域之间的关系。因为子程序域是在主程序域中建立的,因此对该域的控制显然就与Remoting不相同了。

我想先用一副图来表述实现的机制:

说明:
1、Loader类提供创建子程序域和卸载程序域的方法;
2、RemoteLoader类提供装载程序集方法;
3、Loader类获得RemoteLoader类的代理对象,并调用RemoteLoader类的方法;
4、RemoteLoader类的方法在子程序域中完成;
5、Loader类和RemoteLoader类均放在AssemblyLoader.dll程序集文件中;

我们再来看代码:
Loader类:

SetRemoteLoaderObject()方法:

  private AppDomain domain = null;
  private Hashtable domains = new Hashtable();  
  private RemoteLoader rl = null;
public  RemoteLoader SetRemoteLoaderObject( string  dllName)
{
    AppDomainSetup setup 
= new AppDomainSetup();            
    setup.ShadowCopyFiles 
= "true";
    domain 
= AppDomain.CreateDomain(dllName,null,setup);
            
    domains.Add(dllName,domain);    
    
try
    
{
                rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap(
                "AssemblyLoader.dll","AssemblyLoader.RemoteLoader");         
    }

    
catch
    
{
        
throw new Exception();
    }

}


代码中的变量rl为RemoteLoader类对象,在Loader类中是其私有成员。SetRemoteLoaderObject()方法实际上提供了两个功能,一是创建了子程序域,第二则是获得了RemoteLoader类对象。

请大家一定要注意语句:
rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader");

这条语句就是实现两个程序域之间通讯的关键。因为Loader类是在主程序域中,RemoteLoader类则是在子程序域中。如果我们在Loader类即主程序域中显示实例化RemoteLoader类对象rl,此时调用rl的方法,实际上是在主程序域中调用的。因此,我们必须使用代理的方式,来获得rl对象,这就是CreateInstanceFromAndUnwrap方法的目的。其中参数一为要创建类对象的程序集文件名,参数二则是该类的类型名。

CreateCreateInstanceFromAndUnwrap方法有多个重载。代码中的调用方式是当RemoteLoader类为默认构造函数时的其中一种重载。如果RemoteLoader类的构造函数有参数,则方法应改为:

object [] parms  =   {dllName} ;
BindingFlags bindings 
=  BindingFlags.CreateInstance  |
BindingFlags.Instance 
|  BindingFlags.Public;
rl 
=  (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap( " AssemblyLoader.dll " , " AssemblyLoader.RemoteLoader " , true ,bindings,
null ,parms, null , null , null );

详细的调用方式可以参考MSDN。

以下Loader类的Unload方法和LoadAssembly方法():

public  Assembly LoadAssembly( string  dllName)
{
    
try
    
{
        SetRemoteLoaderObject(dllName);
        
return rl.LoadAssembly(dllName);
    }

    
catch (Exception)
    
{
        
throw new AssemblyLoadFailureException();
    }

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

当我们调用Unload方法时,则程序域domain加载的程序集也将随着而被卸载。LoadAssembly方法中的异常AssemblyLoadFailureException为自定义异常:

     public   class  AssemblyLoadFailureException:Exception
    
{
        
public AssemblyLoadFailureException():base()
        
{            
        }


        
public override string Message
        
{
            
get
            
{
                
return "Assembly Load Failure";
            }

        }


    }


既然在Loader类获得的RemoteLoader类实例必须通过代理的方式,因此该类对象必须支持被序列化。所以我们可以令该类派生MarshalByRefObject。RemoteLoader类的代码:

     public   class  RemoteLoader:MarshalByRefObject
    
{
        
public RemoteLoader(string dllName)
        
{
            
if (assembly == null)
            
{
                assembly 
= Assembly.LoadFrom(dllName);
            }

        }
        

        
private Assembly assembly = null;

        
public Assembly LoadAssembly(string dllName)
        
{
            
try
            
{
                assembly 
= Assembly.LoadFrom(dllName);                
                
return assembly;
            }

            
catch (Exception)
            
{
                
throw new AssemblyLoadFailureException();
            }

        }

    }


通过上述的两个类,我们就可以实现程序集的加载和卸载。另外,为了保证应用程序域的对象在内存中被清除,应该令这两个类都实现IDisposable接口,和实现Dispose()方法。

然而在实际的操作过程中,我发现在RemoteLoader类的LoadAssembly方法,是存在遗患的。在我的LoadAssembly方法中,会返回一个Assembly对象。令我百思不得其解的是,虽然都是Assembly对象,但在加载某些程序集并返回Assembly时,在Loader类中会抛出SerializationException异常,并报告反序列化的对象状态不足。这个异常是在序列化获反序列化过程中发生的。我反复比较了两个程序集,一个可以正常加载并序列化,一个会抛出如上异常。会抛出异常的程序集并没有什么特殊之处,且我在程序中的其他地方也没有重复加载该程序集。这是一个疑问!!

不过通常我们在RemoteLoader类中,要实现的方法并非返回一个Assembly对象,而是通过反射加载程序集后,创建该程序集的对象。由于类对象都为object类型,此时序列化就不会出现问题。在我的项目中,因为要获得程序集的版本号,比较版本号在确定是否需要更新,因此我在RemoteLoader类中,只需要在加载程序集后,返回程序集的版本号字符串类型就可以了。字符串类型是绝对支持序列化的。

AssemlbyLoader.Dll的源代码可以点击这里获得。在应用程序中,显示添加对该程序集的引用,然后实例化Loader类对象,来调用该方法即可。我还做了一个简单的测试程序,用的是LoadAssembly方法。大家可以测试一下,是否如我所说,对于某些程序集,可能会抛出序列化的异常!?

测试的代码请点击这里获得,测试界面如下:

同时,大家也可以测试一下,直接加载和通过AppDomain加载,删除程序集文件时会有什么区别?

Unity 独立配置文件Demo //实例化容器 _container = new UnityContainer(); //_container.AddNewExtension(); var unityConfig = System.AppDomain.CurrentDomain.BaseDirectory + @"App_Data\unity.config"; var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = unityConfig }; var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); var unitySection = (UnityConfigurationSection)configuration.GetSection("unity"); _container.LoadConfiguration(unitySection); 立即下载

相关推荐

搭建Dzzoffice 教程

通过以上步骤,您已经成功配置了WEB环境,安装了DzzOffice,并完成了系统设置和应用安装。1.安装包的设置默认是系统盘之外的第二个盘,没有其他盘符的时候则安装在C盘,当主机盘符只有C盘可用的时候,安装时不要使用U盘,否则可能误装到U盘当中。4.如果之前已经安装,再次安装时请确保上次安装已经卸载或者所有文件可读,否则请备份文件,卸载之后再安装。在其他安装环境中,我没有相关的使用经验。3. 保证安装路径是纯净的,安装路径下不能有已安装的V8版本,若重新安装,请选择其它路径。

小胡的博客 4950

通过应用程序域AppDomain加载和卸载程序集

微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外。车门的钥匙只有一把,若要获得还需要你费一些心思。我在学习Remoting的时候,就遇到一个扰人的问题,就是Remoting为远程对象仅提供Register的方法,如果你要注销时,只有另辟蹊径。细心的开发员,会发现Visual Studio.Net中的反射机制,同样面临这个问题。你可以找遍MSDN的所有文档,在Assembly类中,你永...

weixin_34279579的博客 520

保姆级教程:用NovAtel Inertial Explorer 8.7搞定GNSS/INS紧组合后处理(附避坑指南)

本文提供NovAtel Inertial Explorer 8.7的GNSS/INS紧组合后处理全流程教程,涵盖环境准备、数据转换、基站坐标解算及紧组合处理等关键步骤。特别针对零基础用户,分享实用技巧和常见陷阱,帮助高效完成高精度组合导航数据处理,适用于测绘、自动驾驶等领域。

weixin_42614269的博客 279

Unity的程序集Assembly 与 加快代码编译速度

学习大纲 一. 什么是程序集?二. 为什么使用程序集?三. 如何在Unity中使用程序集1.基本常识2.自定义程序集3. 多层级的程序集4. Assembly Definition Reference 四. 程序集 Inspector 面板详解 一. 什么是程序集? 程序集是代码进行编译是的一个逻辑单...

星宇的专栏 3385

手把手教你用STM32CubeMX和HAL库驱动DW3000:从SPI配置到第一个测距Demo

本文详细介绍了如何使用STM32CubeMX和HAL库驱动DW3000 UWB定位通信模块,从SPI配置到实现厘米级测距Demo的全过程。涵盖硬件连接、SPI通信调试、驱动移植及双向测距(TWR)算法实现,帮助开发者快速掌握UWB高精度定位技术,适用于仓储物流、智能家居等场景。

weixin_27038245的博客 485

常见的十大量化投资策略(附源码)

量化投资策略是利用量化的方法,进行金融市场的分析、判断和交易的策略、算法的总称。著名的量化投资策略有以下10种(注:策略源码模板不能直接用于实盘交易,仅供探讨交流) 01、海龟交易策略 海龟交易策略是一套非常完整的趋势跟随型的自动化交易策略。这个复杂的策略在入场条件、仓位控制、资金管理、止损止盈等各个环节,都进行了详细的设计,这基本上可以作为复杂交易策略设计和开发的模板。 海龟交易策略源码:https://www.myquant.cn/docs/python_strategyies/110 0.

小壁虎的春天 2万+

通过应用程序域AppDomain加载和卸载程序集之后,如何再返回原来的主程序域

这样一来,不论如何使用proxy,其实在DefaultAppDomain里面都引用了具体的类和dll,所以导致dll是被当前程序占用的,无论如何都不能被替换。具体实现起来倒也不难,我的问题是:可以加载了,可以卸载了,但是不同域的实例是通过代理来实现调用的(这是可以动态加载/卸载的基础),从主域调用子域中的没问题,反之如何让子域中的类来访问主域中的实例呢?实现目的:动态加载dll,执行完毕之后可以随时卸载掉,并可以替换这些dll,以在运行中更新dll中的类。1:主域中创建子域,加载的dll来自别的文件夹。

biyusr的专栏 460

Kaggle | 总奖池超200万美金的数学竞赛AIMO2冠军方案

••••结论1:通过结合高质量大规模数据集、工具集成推理和生成式选择方法,可以构建在挑战性数学问题上达到最先进水平的语言模型。本文的方法在 AIMO-2 竞赛中的胜利以及在多个数学基准上的 SOTA 结果验证了这一策略的有效性。结论2:发布 OpenMathReasoning 数据集、OpenMath-Nemotron 模型和代码是社区的宝贵财富。

hxshine的博客 1194

C# 通过 AppDomain 应用程序域实现程序集动态卸载或加载

C# 通过 AppDomain 应用程序域实现程序集动态卸载或加载 原文:C# 通过 AppDomain 应用程序域实现程序集动态卸载或加载  AppDomain表示应用程序域,它是一个应用程序在其中执行的独立环境。每个应用程序只有一个主应用程序域,但是一个应用程序可以创建多个子应用程序域。   因此可以通过AppDomain 创建新的应...

weixin_30622107的博客 527

MacOS下Mixly 2.0安装避坑指南:从Java环境到终端权限全解析

本文详细解析了在MacOS系统上安装Mixly 2.0时可能遇到的核心问题与解决方案。针对Java环境版本不兼容、MacOS Gatekeeper安全机制拦截以及终端权限不足三大常见障碍,提供了从检查并配置正确Java版本、安全设置绕过到彻底解决应用内部工具链权限的完整避坑指南,帮助用户顺利完成安装并正常使用。

weixin_29231725的博客 502

基于电阻分压 + 电压跟随器的直流母线电压采样电路

摘要:本文介绍了一种基于电阻分压和电压跟随器的直流母线电压采样电路设计。该电路通过R38(240kΩ)和R41(20kΩ)组成的分压网络将高电压(如48V)降至3V,再经GS8331运放构成的电压跟随器进行隔离缓冲,最后通过RC滤波输出给ADC采集。重点分析了关键元件的选型要求:分压电阻需高精度、低温度系数;运放需高输入阻抗、低失调电压;滤波电容需稳定性好。该设计适用于工业电源、新能源BMS、变频器等需要高精度电压采样的应用场景。

qq_37999850的博客 887
上一篇: Remoting中的方法回调
下一篇: 用C#编写语音自动朗读机器人【转】
msikruby
博客等级 码龄19年 11粉丝 7原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值