假设我有一个AppDomain.AssemblyResolve
事件的处理程序,在处理程序中我构造一个字节数组并调用方法Assembly.Load(byte[])
。此方法本身是否会导致再次引发AssemblyResolve
事件,并导致我的处理程序重新进入?
我的问题不仅限于可以使用C#编译器生成的程序集,它们可以包含CLR支持的abritrary元数据和可执行代码。
我做了一些实验,发现时没有发现任何情况。我尝试加载需要额外引用的程序集,尝试将CAS属性添加到已加载的程序集,其解码需要另一个程序集,尝试使用模块初始值设定项加载程序集(全局.cctor
方法)。在任何情况下,我都没有观察到AssemblyResolve
方法中要引发的Assembly.Load(byte[])
事件,只有在某些代码稍后尝试访问加载的程序集中的类型,方法或属性时才会发生这种情况。但我可能会在这里遗漏一些东西。
答案 0 :(得分:3)
模块初始化程序是我能想到的唯一麻烦制造者。 C ++ / CLI中的一个简单示例:
#include "stdafx.h"
#include <msclr\gcroot.h>
using namespace msclr;
using namespace ClassLibrary10;
class Init {
gcroot<ClassLibrary1::Class1^> managedObject;
public:
Init() {
managedObject = gcnew ClassLibrary1::Class1;
}
} Initializer;
在初始化C运行时之后,通过模块初始化程序加载模块时,将调用Init()构造函数。尽管在特定情况下,Assembly.Load(byte [])无法加载混合模式程序集,但您对此类代码不屑一顾。
这不是由模块初始化器引起的限制。它们被添加到CLR v2.0中,其具体意图是类似这样的工作,在开始执行任何托管代码之前,让语言运行时初始化自己。你遇到这样的代码的几率应该非常非常低。当你看到它时你就会知道它:)
答案 1 :(得分:1)
据我所知Assembly.Load
或通过其他方式加载程序集不会执行可由C#编译器生成的任何构造函数(包括静态构造函数)。因此,您无法在常见程序集上对AssemblyResolve
进行重入。
正如您在问题中提到的那样,在Load
调用期间不会执行模块初始值设定项。 CLI规范中的保证列表 - 摘录可以在张俊峰的Module Initializers中找到。
B中。模块的初始化方法在首次访问模块中定义的任何类型,方法或数据之前或之前执行
有相关的SO问题通常在任何类型构造函数之前讨论&#34;运行代码&#34;比如Initialize library on Assembly load。请注意,.Net: Running code when assembly is loaded得到了Marc Gravell的回答,指出由于安全限制,它可能无法实现。
答案 2 :(得分:1)
AssemblyResolve事件的工作原理:
当您为AssemblyResolve事件注册处理程序时,只要运行时无法按名称绑定到程序集,就会调用该处理程序。例如,从用户代码调用以下方法可能导致引发AssemblyResolve事件:
AppDomain.Load方法重载或Assembly.Load方法重载,其第一个参数是一个字符串,表示要加载的程序集的显示名称(即,由Assembly.FullName属性)。
- 醇>
AppDomain.Load方法重载或Assembly.Load方法重载,其第一个参数是 AssemblyName对象,用于标识要加载的程序集。
没有提到接收byte[]
的重载。我在参考源中查找,似乎接受Load
重载的string
在内部调用名为InternalLoad
的方法,该方法在调用本机LoadImage
调用之前internal static AssemblyName CreateAssemblyName(
String assemblyString,
bool forIntrospection,
out RuntimeAssembly assemblyFromResolveEvent)
{
if (assemblyString == null)
throw new ArgumentNullException("assemblyString");
Contract.EndContractBlock();
if ((assemblyString.Length == 0) ||
(assemblyString[0] == '\0'))
throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength"));
if (forIntrospection)
AppDomain.CheckReflectionOnlyLoadSupported();
AssemblyName an = new AssemblyName();
an.Name = assemblyString;
an.nInit(out assemblyFromResolveEvent, forIntrospection, true); // This method may internally invoke AssemblyResolve event.
return an;
3}}及其文档说明:
创建AssemblyName。如果已引发AssemblyResolve事件,则填充程序集。
byte[]
nLoadImage
重载没有这个,它只是调用QCall.dll
内的原生ResolveEvent
。这可以解释为什么不调用{{1}}。
答案 3 :(得分:1)
你提到过 -
在任何情况下,我都没有观察到要从中提出的AssemblyResolve事件 在Assembly.Load(byte [])方法内部,它只发生了一些代码 后来试图访问加载的类型,方法或属性 部件。但我可能会在这里遗漏一些东西。
这里要注意的要点 -
执行代码时,如果代码中引用了类型且CLR检测到未加载包含该类型的程序集,则它将加载程序集。 你的观察是正确的。
AssemblyResolve是在AppDomain类型中定义的事件。因此,无法从Assembly.Load(byte [])
因此,如果您已在正在运行的appdomain上注册了AssemblyResolve事件并调用Assembly.Load(byte []),则会在当前域中加载程序集。
现在,当调用此加载程序集中的任何类型时,可以说正好调用其他程序集中定义的另一种类型, AppDomain将调用AssemblyResolve事件来尝试加载该程序集。 / p>
答案 4 :(得分:1)
如果执行以下代码:
var appDomain = AppDomain.CreateDomain("some domain");
var assembly = appDomain.Load(someAssemblyBytes);
将生成一个AssemblyResolve
事件。发生这种情况是因为程序集已加载到两个域中。第一个域就是您期望的第一个域-appDomain
,并且不会调用该域的程序集解析器。第二个是您当前的应用程序域,因为您尝试从那里不存在的程序集访问程序集。
因此,如果您从appDomain.Load(someAssemblyName);
以外的其他域执行appDomain
,则将生成两次resolve事件,每个事件针对每个域。
在这种情况下,如果要省略AssemblyResolve尝试(这将失败或将程序集也加载到主域中),则必须创建从MarshalByRefObject
派生的代理类,该代理类将在两者中均可见域并使用此代理之一来包含代理类。即:
internal class AssemblyVersionProxy : MarshalByRefObject
{
public Version GetVersion(byte[] assemblyBytes)
{
var assembly = Assembly.Load(assemblyBytes);
var version = new AssemblyName(assembly.FullName).Version;
return version;
}
}
并使用它:
public Version GetAssemblyVersion(byte[] assemblyBytes)
{
var appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
try
{
var proxyType = typeof(AssemblyVersionProxy);
var proxy = (AssemblyVersionProxy)appDomain.CreateInstanceAndUnwrap(
proxyType.Assembly.FullName, proxyType.FullName,
false, BindingFlags.CreateInstance, null,
new object[0], null, new object[0]
);
var version = proxy.GetVersion(assemblyBytes);
return version;
}
finally
{
AppDomain.Unload(appDomain);
}
}