跨 AppDomain 调用破坏了运行时
Cross-AppDomain call corrupts the runtime
这本来是一个比较冗长的问题,但现在我构建了一个更小的可用示例代码,因此原文不再相关。
我有两个项目,一个包含一个结构,没有成员,名为 TestType。该项目被主项目引用,但程序集不包含在可执行目录中。主项目创建一个新的应用程序域,它在其中使用包含的程序集的名称注册 AssemblyResolve 事件。在主应用程序域中,处理相同的事件,但它手动从项目资源加载程序集。
然后新的应用程序域构建自己的 TestType 版本,但比原始版本具有 多字段 。主应用域使用虚拟版本,新应用域使用生成的版本。
当调用签名中有 TestType 的方法时(即使只是 returning 就足够了),它似乎只是破坏了运行时间并破坏记忆。
我正在使用 .NET 4.5,运行在 x86 下运行。
虚拟程序集:
using System;
[Serializable]
public struct TestType
{
}
主要项目:
using System;
using System.Reflection;
using System.Reflection.Emit;
internal sealed class Program
{
[STAThread]
private static void Main(string[] args)
{
Assembly assemblyCache = null;
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return assemblyCache ?? (assemblyCache = TypeSupport.LoadDummyAssembly(name.Name));
}
return null;
};
Start();
}
private static void Start()
{
var server = ServerObject.Create();
//prints 155680
server.TestMethod1("Test");
//prints 0
server.TestMethod2("Test");
}
}
public class ServerObject : MarshalByRefObject
{
public static ServerObject Create()
{
var domain = AppDomain.CreateDomain("TestDomain");
var t = typeof(ServerObject);
return (ServerObject)domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName);
}
public ServerObject()
{
Assembly genAsm = TypeSupport.GenerateDynamicAssembly("DummyAssembly");
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return genAsm;
}
return null;
};
}
public TestType TestMethod1(string v)
{
Console.WriteLine(v.Length);
return default(TestType);
}
public void TestMethod2(string v)
{
Console.WriteLine(v.Length);
}
}
public static class TypeSupport
{
public static Assembly LoadDummyAssembly(string name)
{
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
if(stream != null)
{
var data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
return Assembly.Load(data);
}
return null;
}
public static Assembly GenerateDynamicAssembly(string name)
{
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(name), AssemblyBuilderAccess.Run
);
var mod = ab.DefineDynamicModule(name+".dll");
var tb = GenerateTestType(mod);
tb.CreateType();
return ab;
}
private static TypeBuilder GenerateTestType(ModuleBuilder mod)
{
var tb = mod.DefineType("TestType", TypeAttributes.Public | TypeAttributes.Serializable, typeof(ValueType));
for(int i = 0; i < 3; i++)
{
tb.DefineField("_"+i.ToString(), typeof(int), FieldAttributes.Public);
}
return tb;
}
}
虽然 TestMethod1 和 TestMethod2 都应该打印 4,但第一个访问内存的一些奇怪部分,并且似乎破坏了调用堆栈足以影响对第二个方法的调用。如果我删除对第一个方法的调用,一切都很好。
如果我 运行 x64 下的代码,第一个方法抛出 NullReferenceException.
两个结构的字段数量似乎很重要。如果第二个结构总的来说比第一个大(如果我只生成一个字段或 none),一切也都正常,如果 DummyAssembly 中的结构包含更多领域。这让我相信 JITter 要么错误地编译了方法(没有使用生成的程序集),要么调用了错误的本机版本的方法。我检查了 typeof(TestType)
returns 类型的正确(生成)版本。
总而言之,我没有使用任何不安全的代码,所以这不应该发生。
我能够在我的机器上用最新的框架重现这个问题。
我在默认应用程序域的程序集解析中添加了版本检查:
if (name.Name == "DummyAssembly" && name.Version.Major == 1)
我遇到了以下异常:
System.Runtime.Serialization.SerializationException: Cannot find assembly 'DummyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Server stack trace:
w System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
w System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
w System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.FixupForNewAppDomain()
w System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
w System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
w System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
w ServerObject.TestMethod1(TestType& result, String v)
Binary formatter 在这里用于Marshaling,它从不同的AppDomains 中找到不同大小的值类型。请注意,当您调用 TestMethod1
时,它会尝试使用版本 0.0.0.0
加载您的 DummyAssembly
,并向它传递您早期缓存的虚拟版本 1.0.0.0
,其中 TestType
尺寸不同。
由于不同大小的结构,当您 return 通过方法中的值时,AppDomains
之间的编组出现问题并且堆栈变得不平衡(可能是运行时中的错误?)。通过引用返回似乎没有问题(引用的大小始终相同)。
使两个程序集中的结构大小相等/return通过引用应该可以解决这个问题。
这本来是一个比较冗长的问题,但现在我构建了一个更小的可用示例代码,因此原文不再相关。
我有两个项目,一个包含一个结构,没有成员,名为 TestType。该项目被主项目引用,但程序集不包含在可执行目录中。主项目创建一个新的应用程序域,它在其中使用包含的程序集的名称注册 AssemblyResolve 事件。在主应用程序域中,处理相同的事件,但它手动从项目资源加载程序集。
然后新的应用程序域构建自己的 TestType 版本,但比原始版本具有 多字段 。主应用域使用虚拟版本,新应用域使用生成的版本。
当调用签名中有 TestType 的方法时(即使只是 returning 就足够了),它似乎只是破坏了运行时间并破坏记忆。
我正在使用 .NET 4.5,运行在 x86 下运行。
虚拟程序集:
using System;
[Serializable]
public struct TestType
{
}
主要项目:
using System;
using System.Reflection;
using System.Reflection.Emit;
internal sealed class Program
{
[STAThread]
private static void Main(string[] args)
{
Assembly assemblyCache = null;
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return assemblyCache ?? (assemblyCache = TypeSupport.LoadDummyAssembly(name.Name));
}
return null;
};
Start();
}
private static void Start()
{
var server = ServerObject.Create();
//prints 155680
server.TestMethod1("Test");
//prints 0
server.TestMethod2("Test");
}
}
public class ServerObject : MarshalByRefObject
{
public static ServerObject Create()
{
var domain = AppDomain.CreateDomain("TestDomain");
var t = typeof(ServerObject);
return (ServerObject)domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName);
}
public ServerObject()
{
Assembly genAsm = TypeSupport.GenerateDynamicAssembly("DummyAssembly");
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return genAsm;
}
return null;
};
}
public TestType TestMethod1(string v)
{
Console.WriteLine(v.Length);
return default(TestType);
}
public void TestMethod2(string v)
{
Console.WriteLine(v.Length);
}
}
public static class TypeSupport
{
public static Assembly LoadDummyAssembly(string name)
{
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
if(stream != null)
{
var data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
return Assembly.Load(data);
}
return null;
}
public static Assembly GenerateDynamicAssembly(string name)
{
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(name), AssemblyBuilderAccess.Run
);
var mod = ab.DefineDynamicModule(name+".dll");
var tb = GenerateTestType(mod);
tb.CreateType();
return ab;
}
private static TypeBuilder GenerateTestType(ModuleBuilder mod)
{
var tb = mod.DefineType("TestType", TypeAttributes.Public | TypeAttributes.Serializable, typeof(ValueType));
for(int i = 0; i < 3; i++)
{
tb.DefineField("_"+i.ToString(), typeof(int), FieldAttributes.Public);
}
return tb;
}
}
虽然 TestMethod1 和 TestMethod2 都应该打印 4,但第一个访问内存的一些奇怪部分,并且似乎破坏了调用堆栈足以影响对第二个方法的调用。如果我删除对第一个方法的调用,一切都很好。
如果我 运行 x64 下的代码,第一个方法抛出 NullReferenceException.
两个结构的字段数量似乎很重要。如果第二个结构总的来说比第一个大(如果我只生成一个字段或 none),一切也都正常,如果 DummyAssembly 中的结构包含更多领域。这让我相信 JITter 要么错误地编译了方法(没有使用生成的程序集),要么调用了错误的本机版本的方法。我检查了 typeof(TestType)
returns 类型的正确(生成)版本。
总而言之,我没有使用任何不安全的代码,所以这不应该发生。
我能够在我的机器上用最新的框架重现这个问题。
我在默认应用程序域的程序集解析中添加了版本检查:
if (name.Name == "DummyAssembly" && name.Version.Major == 1)
我遇到了以下异常:
System.Runtime.Serialization.SerializationException: Cannot find assembly 'DummyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Server stack trace:
w System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
w System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
w System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.FixupForNewAppDomain()
w System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
w System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
w System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
w ServerObject.TestMethod1(TestType& result, String v)
Binary formatter 在这里用于Marshaling,它从不同的AppDomains 中找到不同大小的值类型。请注意,当您调用 TestMethod1
时,它会尝试使用版本 0.0.0.0
加载您的 DummyAssembly
,并向它传递您早期缓存的虚拟版本 1.0.0.0
,其中 TestType
尺寸不同。
由于不同大小的结构,当您 return 通过方法中的值时,AppDomains
之间的编组出现问题并且堆栈变得不平衡(可能是运行时中的错误?)。通过引用返回似乎没有问题(引用的大小始终相同)。
使两个程序集中的结构大小相等/return通过引用应该可以解决这个问题。