c#

位置:IT落伍者 >> c# >> 浏览文章

关于C#中动态加载AppDomain的问题


发布日期:2019年11月03日
 
关于C#中动态加载AppDomain的问题

在操作系统中利用进程可以对正在运行的应用程序进行隔离每个应用程序被加载到单独的进程中并为其分配虚拟内存进程无法直接访问物理内存只能通过操作系统将虚拟内存映射到物理内存中并保证进程之间的物理内存不会重叠但是进程最大的缺点就是效率问题尤其是进程的切换开销很大而进程间不能共享内存所以不可能从一个进程通过传递指针给另一个进程

NET中出现了一个新的概念AppDomain——应用程序域所有NET应用程序都需要运行在托管环境中操作系统能提供的只有进程因此NET程序需要通过AppDomain这个媒介来运行在进程中同时使用该incheng提供的内存空间只要是NET的应用都会运行在某个AppDomain中

当我们运行一个NET应用程序或者运行库宿主时OS会首先建立一个进程然后会在进程中加载CLR(这个加载一般是通过调用_CorExeMain或者_CorBindToRuntimeEx方法来实现)在加载CLR时会创建一个默认的AppDomain它是CLR的运行单元程序的Main方法就是在这里执行这个默认的AppDomain是唯一且不能被卸载的当该进程消灭时默认AppDomain才会随之消失

一个进程中可以有多个AppDomain且它们直接是相互隔离的我们的Assembly是不能单独执行的它必须被加载到某个AppDomain中要想卸载一个Assembly就只能卸载其AppDomain最近在我所参加的一个项目中要实现这样一个模块定制一个作业管理器它可以定时的以不同频率执行某些Net应用程序或者存储过程这里的频率可以是仅一次每天每周还是每月进行执行计划的实施对于调用存储过程没什么好说的但是调用Net应用程序的时候就需要考虑如下问题一旦Assembly被作业管理器的服务器调用(比如某个执行计划正好要被执行了)在调用之前会将程序集加载到默认AppDomain然后执行这就有个问题如果我需要做替换或者删除Assembly等这些操作的时候由于Assembly已经被默认AppDomain加载那么对它的更改肯定是不允许的它会弹出这样的错误

除非你关掉作业管理服务器然后再操作显然这样做是很不合理的

并且默认AppDomain是不能被卸载的那么我们该怎么办呢我想到的方法是动态的加载Assembly新建一个AppDomain让Assembly加载到这个新AppDomain中然后执行当执行完后卸载这个新的AppDomain即可方法如下

创建程序集加载类AssemblyDynamicLoader该类用来创建新的AppDomain并生成用来执行Net程序的RemoteLoader类:

using System;

using SystemCollectionsGeneric;

using SystemGlobalization;

using SystemIO;

using SystemReflection;

using SystemText;

using ArkLog;

/// <summary>

/// The local loader

/// </summary>

public class AssemblyDynamicLoader

{

/// <summary>

/// The log util

/// </summary>

private static ILog log = LogManagerGetLogger(typeof(AssemblyDynamicLoader));

/// <summary>

/// The new appdomain

/// </summary>

private AppDomain appDomain;

/// <summary>

/// The remote loader

/// </summary>

private RemoteLoader remoteLoader;

/// <summary>

/// Initializes a new instance of the <see cref=LocalLoader/> class

/// </summary>

public AssemblyDynamicLoader()

{

AppDomainSetup setup = new AppDomainSetup();

setupApplicationName = ApplicationLoader;

setupApplicationBase = AppDomainCurrentDomainBaseDirectory;

setupPrivateBinPath = PathCombine(AppDomainCurrentDomainBaseDirectory private);

setupCachePath = setupApplicationBase;

setupShadowCopyFiles = true;

setupShadowCopyDirectories = setupApplicationBase;

thisappDomain = AppDomainCreateDomain(ApplicationLoaderDomain null setup);

String name = AssemblyGetExecutingAssembly()GetName()FullName;

thisremoteLoader = (RemoteLoader)thisappDomainCreateInstanceAndUnwrap(name typeof(RemoteLoader)FullName);

}

/// <summary>

/// Invokes the method

/// </summary>

/// <param name=fullName>The full name</param>

/// <param name=className>Name of the class</param>

/// <param name=argsInput>The args input</param>

/// <param name=programName>Name of the program</param>

/// <returns>The output of excuting</returns>

public String InvokeMethod(String fullName String className String argsInput String programName)

{

thisremoteLoaderInvokeMethod(fullName className argsInput programName);

return thisremoteLoaderOutput;

}

/// <summary>

/// Unloads this instance

/// </summary>

public void Unload()

{

try

{

AppDomainUnload(thisappDomain);

thisappDomain = null;

}

catch (CannotUnloadAppDomainException ex)

{

logError(To unload assembly error! ex);

}

}

}

创建RemoteLoader类它可以在AppDomain中自由穿越这就需要继承SystemMarshalByRefObject这个抽象类这里RemoteLoader如果不继承MarshalByRefObject类则一定会报错

(在不同AppDomain间传递对象该对象必须是可序列化的)以RemoteLoader类做为代理来调用待执行的Net程序

using System;

using SystemCollectionsGeneric;

using SystemGlobalization;

using SystemIO;

using SystemReflection;

using SystemText;

/// <summary>

/// The Remote loader

/// </summary>

public class RemoteLoader : MarshalByRefObject

{

/// <summary>

/// The assembly we need

/// </summary>

private Assembly assembly = null;

/// <summary>

/// The output

/// </summary>

private String output = StringEmpty;

/// <summary>

/// Gets the output

/// </summary>

/// <value>The output</value>

public String Output

{

get

{

return thisoutput;

}

}

/// <summary>

/// Invokes the method

/// </summary>

/// <param name=fullName>The full name</param>

/// <param name=className>Name of the class</param>

/// <param name=argsInput>The args input</param>

/// <param name=programName>Name of the program</param>

public void InvokeMethod(String fullName String className String argsInput String programName)

{

thisassembly = null;

thisoutput = StringEmpty;

try

{

thisassembly = AssemblyLoadFrom(fullName);

Type pgmType = null;

if (thisassembly != null)

{

pgmType = thisassemblyGetType(className true true);

}

else

{

pgmType = TypeGetType(className true true);

}

Object[] args = RunJobGetArgs(argsInput);

BindingFlags defaultBinding = BindingFlagsDeclaredOnly | BindingFlagsPublic

| BindingFlagsNonPublic | BindingFlagsInstance | BindingFlagsIgnoreCase

| BindingFlagsInvokeMethod | BindingFlagsStatic;

CultureInfo cultureInfo = new CultureInfo(esES false);

try

{

MethodInfo methisInfo = RunJobGetItsMethodInfo(pgmType defaultBinding programName);

if (methisInfo == null)

{

thisoutput = EMethod does not exist!;

}

if (methisInfoIsStatic)

{

if (methisInfoGetParameters()Length == )

{

if (methisInfoReturnType == typeof(void))

{

pgmTypeInvokeMember(programName defaultBinding null null null cultureInfo);

thisoutput = STo call a method without return value successful;

}

else

{

thisoutput = (String)pgmTypeInvokeMember(programName defaultBinding null null null cultureInfo);

}

}

else

{

if (methisInfoReturnType == typeof(void))

{

pgmTypeInvokeMember(programName defaultBinding null null args cultureInfo);

thisoutput = STo call a method without return value successful;

}

else

{

thisoutput = (String)pgmTypeInvokeMember(programName defaultBinding null null args cultureInfo);

}

}

}

else

{

if (methisInfoGetParameters()Length == )

{

object pgmClass = ActivatorCreateInstance(pgmType);

if (methisInfoReturnType == typeof(void))

{

pgmTypeInvokeMember(programName defaultBinding null pgmClass null cultureInfo);

thisoutput = STo call a method without return value successful;

}

else

{

thisoutput = (String)pgmTypeInvokeMember(programName defaultBinding null pgmClass null cultureInfo); //ymtpgm is programs name and the return value of it must be started with O

}

}

else

{

object pgmClass = ActivatorCreateInstance(pgmType);

if (methisInfoReturnType == typeof(void))

{

pgmTypeInvokeMember(programName defaultBinding null pgmClass args cultureInfo);

thisoutput = STo call a method without return value successful;

}

else

{

thisoutput = (String)pgmTypeInvokeMember(programName defaultBinding null pgmClass args cultureInfo); //ymtpgm is programs name and the return value of it must be started with O

}

}

}

}

catch

{

thisoutput = (String)pgmTypeInvokeMember(programName defaultBinding null null null cultureInfo);

}

}

catch (Exception e)

{

thisoutput = E + eMessage;

}

}

}

其中的InvokeMethod方法只要提供Assembly的全名类的全名待执行方法的输入参数和

其全名就可以执行该方法该方法可以是带参数或不带参数静态的或者不是静态的

最后这样使用这两个类

AssemblyDynamicLoader loader = new AssemblyDynamicLoader();

String output = loaderInvokeMethod(fileName ymtcla yjoinp ymtpgm);

loaderUnload();

               

上一篇:浅谈.NET重写URL

下一篇:谈基于.net平台开发中的模式窗体