【发布时间】:2026-02-09 08:30:01
【问题描述】:
这原本是一个更长的问题,但现在我已经构建了一个更小的可用示例代码,所以原文不再相关。
我有两个项目,一个包含一个无成员的结构,名为TestType。该项目被主项目引用,但程序集不包含在可执行目录中。主项目创建一个新的应用程序域,在其中使用包含的程序集的名称注册 AssemblyResolve 事件。在主应用程序域中,处理相同的事件,但它手动从项目资源加载程序集。
然后,新的应用程序域会构建自己的 TestType 版本,但比原来的具有更多字段。主应用域使用虚拟版本,新应用域使用生成的版本。
当调用签名中包含 TestType 的方法时(即使只是简单地返回就足够了),它似乎只是破坏了运行时的稳定性并破坏了内存。
我使用的是 .NET 4.5,在 x86 下运行。
DummyAssembly:
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。
这两个结构的字段数量似乎很重要。如果第二个结构总体上比第一个大(如果我只生成一个字段或不生成),那么一切都可以正常工作,如果 DummyAssembly 中的结构包含更多字段。这让我相信 JITter 要么错误地编译了方法(不使用生成的程序集),要么调用了不正确的本机版本的方法。我检查了typeof(TestType) 返回的类型的正确(生成)版本。
总而言之,我没有使用任何不安全的代码,所以这不应该发生。
【问题讨论】:
-
你需要真正的minimal reproducible example 才能让别人看到你的问题......旁注:事实上你有 struct
Vect没有任何值类型字段,因为你大概会感到困惑对 .Net 内部有很好的理解,包括与从字节加载程序集相关的程序集标识问题...... -
为什么不把你的结构定义为不安全的结构{双坐标[DIMENSION_COUNT]; }?然后你可以把它的地址作为一个 long 或其他东西传递给另一个 AppDomain,只要它存在于同一个进程中,它就可以很好地读取它。
-
@AlexeiLevenkov 我提供的代码可用于验证问题,并包含重现问题所需的所有代码。虚拟 Vect 类型只需存储坐标并将它们序列化到动态应用程序域。它最初也有一个索引器,但我删除了它以减少此处代码的大小。真正的向量操作发生在动态类型上,它当然有固定数量的
double字段 (vtyp.DefineField),您可以使用dynamic访问这些字段,方法是我现在添加到问题中。 -
@hoodaticus 我想访问在动态应用程序域中使用
Vect的任何方法或属性,而无需在调用站点解决序列化问题。我也可以将double[]直接传递给该方法。此外,DIMENSION_COUNT 不能是编译类型常量,因为用户必须能够在运行时更改它。 -
嗯,我想知道
new double[0]可能隐藏了什么错误。例外是你的朋友。
标签: c# .net serialization appdomain remoting