How to load an assembly from byte array into a non-default AppDomain without accessing the app directory?

dym*_*oid 7 .net c# appdomain assembly-loading .net-assembly

I have an in-memory assembly MyAssembly (class library) that is used in my main assembly MyApp.exe:

byte[] assemblyData = GetAssemblyDataFromSomewhere();
Run Code Online (Sandbox Code Playgroud)

(For testing, the GetAssemblyDataFromSomewhere method can just do File.ReadAllBytes for an existing assembly file, but in my real app there is no file.)

MyAssembly has only .NET Framework references and has no dependencies to any other user code.

I can load this assembly into the current (default) AppDomain:

Assembly.Load(assemblyData);

// this works
var obj = Activator.CreateInstance("MyAssembly", "MyNamespace.MyType").Unwrap();
Run Code Online (Sandbox Code Playgroud)

Now, I want to load this assembly into a different AppDomain and instantiate the class there. MyNamespace.MyType is derived from MarshalByRefObject, so I can share the instance across the app domains.

var newAppDomain = AppDomain.CreateDomain("DifferentAppDomain");

// this doesn't really work...
newAppDomain.Load(assemblyData);

// ...because this throws a FileNotFoundException
var obj = newAppDomain.CreateInstanceAndUnwrap("MyAssembly", "MyNamespace.MyType");
Run Code Online (Sandbox Code Playgroud)

Yes, I know there is a note in the AppDomain.Load docs:

This method should be used only to load an assembly into the current application domain.

Yes, it should be used for that, but...

If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains.

I can live with that. There's no problem for me if the assembly will be loaded into both app domains (because I actually load it into the default app domain anyway).

I can see that assembly loaded into the new app domain. Kind of.

var assemblies = newAppDomain.GetAssemblies().Select(a => a.GetName().Name);
Console.WriteLine(string.Join("\r\n", assemblies));
Run Code Online (Sandbox Code Playgroud)

This gives me:

mscorlib
MyAssembly
Run Code Online (Sandbox Code Playgroud)

But trying to instantiate the class always leads to a FileNotFoundException, because the CLR tries to load the assembly from file (despite it is already loaded, at least according to AppDomain.GetAssemblies).

I could do this in MyApp.exe:

newAppDomain.AssemblyResolve += CustomResolver;

private static Assembly CustomResolver(object sender, ResolveEventArgs e)
{
    byte[] assemblyData = GetAssemblyDataFromSomewhere();
    return Assembly.Load(assemblyData);
}
Run Code Online (Sandbox Code Playgroud)

This works, but this causes the second app domain to load the calling assembly (MyApp.exe) from file. It happens because that app domain now needs the code (the CustomResolver method) form the calling assembly.

我可以将应用程序域创建逻辑和事件处理程序移动到不同的程序集中,例如MyAppServices.dll,这样新的应用程序域将加载该程序集而不是MyApp.exe.

但是,我想不惜一切代价避免文件系统访问我的应用程序目录:新的应用程序域不得从文件加载任何用户程序集。

我也试过AppDomain.DefineDynamicAssembly,但这也不起作用,因为返回值的类型System.Reflection.Emit.AssemblyBuilder既没有MarshalByRefObject也没有标有[Serializable].

有没有办法将字节数组中的程序集加载到非默认值中,AppDomain而无需将文件中的调用程序集加载到该应用程序域中?实际上,没有任何文件系统访问我的应用程序目录?

Ary*_*dlé 0

我不太确定你想实现什么目标,但我会尝试以下操作。

总的来说,你的方法似乎没问题。您必须确保辅助应用程序域的探测路径(尤其是应用程序库路径)设置正确。否则,.NET Fusion 将探测这些位置的依赖关系,并且您将尝试避免不必要的文件系统访问尝试。(好吧,至少确保这些路径配置为一些没有设置实际权限的临时文件夹)。

建议的解决方案

在任何情况下,您都可以尝试向动态(我应该这样称呼它吗?)程序集添加一个入口点(例如Main某些类中的方法Bootstrap),并在将程序集加载到辅助 AppDomain 后尝试调用AppDomain.ExecuteAssemblyByName 。

Bootstrap我会将您的方法添加到类中CustomResolver,并在该Main方法中,我将订阅AssemblyResolve.

这样,当Main调用该方法时(希望它按预期工作),对 AppDomain 的订阅AssemblyResolve将不会触发融合。

我没有测试这个解决方案,它可能是一个远景,但更糟糕的尝试。

PS: 我确实看到有关此方法的文档确实指出,运行时将尝试首先加载程序集(可能使用常规探测逻辑),但它没有说明程序集预加载到的情况调用之前的 AppDomain。

评论

ExecuteAssemblyByName 方法提供与 ExecuteAssembly 方法类似的功能,但通过显示名称或 AssemblyName 而不是通过文件位置指定程序集。因此,ExecuteAssemblyByName 使用Load方法而不是 LoadFile 方法加载程序集。

程序集在 .NET Framework 标头中指定的入口点开始执行。

此方法不会创建新的进程或应用程序域,并且不会在新线程上执行入口点方法。

加载方法文档也没有提供明确的答案。

PPS: 调用Unwrap方法可能会触发主 AppDomain 中的融合,因为为您的类创建了代理。我认为,此时您的主 AppDomain 将尝试找到该动态加载的程序集。您确定是辅助 AppDomain 引发了异常吗?