Первоначально это был гораздо более длительный вопрос, но теперь я создал более компактный примерный код, поэтому исходный текст больше не имеет отношения.
У меня есть два проекта, один из которых содержит одну структуру с без членов, с именем 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)
возвращает правильную (сгенерированную) версию типа.
В общем, я не использую какой-либо небезопасный код, поэтому этого не должно быть.