1

动机。我有一个客户端-服务器应用程序。在某些时候,服务器端会根据某些元数据动态创建一个新类型,客户端无法使用。服务器需要向客户端发送该类型的实例。但是,客户端将无法反序列化实例,因为它的类型未知。

一种解决方案是将元数据和数据捆绑在一起,传输到客户端并让它重新创建动态类型和实例。

当特定实例深深嵌套在对象图中时,事情会变得一团糟。我想做的是将对象图按原样发送给客户端,让反序列化代码触发 AppDomain.AssemblyResolved 事件并在那里重新创建相应的动态类型。唉! 我不能这样做,因为我不知道如何使元数据可用于事件处理程序。

我尝试使用 CallContext,但它不起作用。

这是我用来寻找解决方案的完整示例代码,但我没有成功:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Security;
using System.Security.Permissions;

namespace DynamicTypes
{
  [Serializable]
  public class LogicalCallContextData : ILogicalThreadAffinative
  {
    public string DynamicAssemblyName { get; private set; }
    public string DynamicTypeName { get; private set; }

    public LogicalCallContextData(string dynamicAssemblyName, string dynamicTypeName)
    {
      DynamicAssemblyName = dynamicAssemblyName;
      DynamicTypeName = dynamicTypeName;
    }
  }

  class Program
  {
    private static string DynamicAssemblyName;
    private static string DynamicTypeName;
    private static Type m_type;

    static void CreateDynamicType()
    {
      if (m_type == null)
      {
        var assemblyName = new AssemblyName(DynamicAssemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var typeBuilder = moduleBuilder.DefineType(DynamicTypeName, TypeAttributes.Public | TypeAttributes.Serializable, typeof(object));
        var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
        var ilGenerator = constructorBuilder.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Call, typeof(object).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null));
        ilGenerator.Emit(OpCodes.Ret);
        m_type = typeBuilder.CreateType();
      }
    }

    static void AppDomainInitialize(string[] args)
    {
      AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
    }

    static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
    {
      var data = (LogicalCallContextData)CallContext.GetData("test data");
      if (data != null)
      {
        DynamicAssemblyName = data.DynamicAssemblyName;
        DynamicTypeName = data.DynamicTypeName;

        CreateDynamicType();
        if (m_type.Assembly.FullName == args.Name)
        {
          return m_type.Assembly;
        }
      }
      return null;
    }

    [Serializable]
    private class CrossAppDomain
    {
      private object m_obj;
      public CrossAppDomain()
      {
        CreateDynamicType();
        m_obj = Activator.CreateInstance(m_type);
      }

      public void DoIt()
      {
      }
    }

    [PermissionSet(SecurityAction.LinkDemand)]
    static void Main(string[] args)
    {
      DynamicAssemblyName = Guid.NewGuid().ToString("N");
      DynamicTypeName = Guid.NewGuid().ToString("N");

      var data = new LogicalCallContextData(DynamicAssemblyName, DynamicTypeName);
      CallContext.SetData("test data", data);

      AppDomainInitialize(null);
      var appDomainSetup = new AppDomainSetup();
      appDomainSetup.AppDomainInitializer = AppDomainInitialize;
      var appDomain = AppDomain.CreateDomain("second", null, appDomainSetup);
      appDomain.DoCallBack(new CrossAppDomain().DoIt);
    }
  }
}

事件处理程序中返回data的是.OnAssemblyResolvenull

有谁知道该怎么做?

编辑:可以在两次往返中进行 - 第一次传递元数据,第二次传递对象本身。我想找到一个往返解决方案。

编辑:2我想出了一个绝对疯狂的解决方案。它有效,但我想知道性能影响。如果我为每个动态类型只创建一个动态程序集并以该程序集的名称对类型的元数据进行编码会怎样?我检查了这种方法,它似乎有效。我得到了长达 500 个字符的程序集名称。每个程序集都定义了单个模块“DynamicModule”和单个类型 - “DynamicType”。我仍然期待更好的解决方案。

4

1 回答 1

0

您可以将非静态方法注册为 AppDomain.AssemblyResolve 事件处理程序。然后您可以访问实例的成员,以及注册了哪个方法。这很像我在这里介绍的 AssemblyResolver 类:

DisallowApplicationBaseProbing = true 时需要连接 AssemblyResolve 事件

在反序列化时,您可以在触发 AssemblyResolve 事件之前将元数据存储在 AssemblyResolver 实例中。有趣的一点是“何时”将元数据存储到 AssemblyResolver。坚持单次反序列化运行需要您在包含动态类型对象的对象中实现元数据的反序列化。为了方便起见,也许您可​​以将动态对象放在某种包装器中。让包装器带来元数据和动态类型对象(后者序列化为字符串或字节 [],具体取决于您的序列化)。自定义包装器的反序列化过程,首先将元数据推送到 AssemblyResolver。然后从包装器的 string 或 byte[] 成员中反序列化动态类型的对象。

访问 AssemblyResolver 的最简单的解决方案可能是单例模式,尽管许多人投票支持依赖注入。

实际上,您将对对象结构的某些部分“本地”运行递归反序列化。但是,我没有看到对高级对象结构反序列化的任何影响。请注意,此解决方案需要一些额外的工作才能获得线程安全,因为您需要在推送元数据之前阻止 AssemblyResolver。如果有一个动态类型对象持有另一个动态类型对象,则会出现问题,因为您需要在 AssemblyResolve 事件处理结束时释放 AssemblyResolver。

于 2014-01-23T15:00:39.397 回答