【发布时间】:2016-02-18 11:56:54
【问题描述】:
如果标题没有意义,我提前道歉。我对 appdomains 和程序集加载非常陌生,并且真的不知道如何陈述我要问的内容。
我一直在忙于在运行时将嵌入式 DLL 加载到应用程序中,但我似乎无法弄清楚为什么它以一种方式工作,而另一种则不行。似乎如果您尝试将 DLL(从字节数组)加载到当前的 appdomain 中,之后创建的任何对象/线程都将能够解析对新加载的库的引用,但是原始上下文中的对象将无法解析新加载的库。
这是我的示例库,它将在运行时作为嵌入式资源加载(需要对 MessageBox 的 WPF PresentationFramework.dll 的引用):
namespace LoaderLibrary
{
public class LoaderLibrary
{
public static void Test()
{
System.Windows.MessageBox.Show("success");
}
}
}
在我的控制台应用程序 .csproj 文件中,我为该项目手动添加以下嵌入式资源,并还包括对 LoaderLibrary 的项目引用:
<ItemGroup>
<EmbeddedResource Include="..\LoaderLibrary\bin\$(Configuration)\LoaderLibrary.dll">
<LogicalName>EmbeddedResource.LoaderLibrary.dll</LogicalName>
</EmbeddedResource>
</ItemGroup>
这是加载该库的控制台应用程序的代码(需要对 LoaderLibrary csproj 的项目引用)ALSO:需要设置 CopyLocal对于 LoaderLibrary 参考,strong> 到 false:
namespace AssemblyLoaderTest
{
class Program
{
static void Main(string[] args)
{
EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };
var app = new TestApp();
}
}
public class TestApp
{
public TestApp()
{
LoaderLibrary.LoaderLibrary.Test();
}
}
public class EmbeddedAssembly
{
static System.Collections.Generic.Dictionary<string, System.Reflection.Assembly> assemblies = new System.Collections.Generic.Dictionary<string, System.Reflection.Assembly>();
public static void Load(string embeddedResource)
{
using (System.IO.Stream stm = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResource))
using (var mstream = new System.IO.MemoryStream())
{
stm.CopyTo(mstream);
var assembly = System.Reflection.Assembly.Load(mstream.ToArray());
assemblies.Add(assembly.FullName, assembly);
return;
}
}
public static System.Reflection.Assembly Get(string assemblyFullName)
{
return (assemblies.Count == 0 || !assemblies.ContainsKey(assemblyFullName)) ? null : assemblies[assemblyFullName];
}
}
}
此代码能够成功加载并执行 LoaderLibrary.LoaderLibrary.Test() 函数。
我的问题是为什么以下不起作用?
static void Main(string[] args)
{
EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };
LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code
}
这也行不通:
static void Main(string[] args)
{
EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };
var app = new TestApp();
LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code
}
【问题讨论】:
-
程序集由即时编译器加载。它需要将您的 Main() 方法转换为机器代码,然后才能开始运行。这当然发生在它运行之前,因此 AssemblyResolve 还不能做任何事情来帮助找到程序集。在发布版本中变得更糟,优化器期待内联方法的机会。您需要另一种方法,例如 RealMain(),将 Test() 调用移到该方法中。并赋予它 [MethodImpl(MethodImplOptions.Nolinling)] 属性以减慢优化器的速度。
-
所以基本上它需要任何函数作为入口点并将其转换为 IL,然后再尝试执行任何代码?并且由于该程序集在技术上尚未添加到 AppDomain 中,因此会导致依赖项解析失败?
-
好的,我很确定我现在明白了。基本上在入口点的 JIT 编译期间,它无法解决依赖关系,因为它显然无法在编译之前执行任何代码,从而导致我看到的错误。将对动态加载的程序集的调用移动到不同的函数范围并使用 [MethodImpl(MethodImplOptions.Nolinling)] 标记调用它们的方法有助于防止优化器将引用动态加载的程序集的代码移动到将在加载程序集之前进行编译。
标签: c# .net reflection appdomain