Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

基于透明代理的内部类访问抽象 [1] 类型与调用的封装

Posted on 2005-02-08 23:52  Flier Lu  阅读(2412)  评论(3编辑  收藏  举报
http://www.blogcn.com/User8/flier_lu/blog/6186446.html


    按照 MS 的通常思维习惯,.NET 在自带的类库中保留了很多被标记为 internal 的工具类。为了更简洁或更直接的达到目的,我们往往需要牺牲一定的兼容性,通过调用这些内部类来简化我们的工作。甚至有些工作不通过内部类根本无法完成,如我以前在一篇《正确判断当前用户角色》文章中使用的方法,就可以在 hack 一下内部实现后,大大减轻调试和测试的工作量,而通过正规方式实现非常繁琐。
    但是按照标准方法通过 Reflection 调用内部类及其方法,编写代码极其繁琐;而且因为大量操作是通过非强类型的字符串或对象数组,造成很多问题只有到运行时才能发现;同时因为重复代码到处散步,代码的可测试性也很差。
    如《正确判断当前用户角色》一文中获取角色名称的函数调用代码如下:
以下为引用:

WindowsIdentity identity = WindowsIdentity.GetCurrent();

MethodInfo method = identity.GetType().GetMethod("GetRoles", BindingFlags.Instance | BindingFlags.NonPublic);

String[] roleNames = (String[])method.Invoke(identity, new object[] {});

foreach(string roleName in roleNames)
{
  Console.WriteLine(roleName);
}


    这是一段非常典型的通过反射调用内部方法的实现代码,需要通过 GetMethod 获取方法,再手工 Invoke,最后将返回值强制转换。为了解决这个问题,我们希望能够通过某种方式,以强类型的预定义方法定义跨越 internal 限制,最终达到如下代码的效果:
以下为引用:

public abstract class WindowsIdentityObject : InternalObject
{
  public abstract string[] GetRoles();
}

WrapperManager.RegisterWrapper(typeof(WindowsIdentity).FullName, null, typeof(WindowsIdentityObject));

WindowsIdentityObject identity = (WindowsIdentityObject)WrapperManager.CreateWrapperOfObject(WindowsIdentity.GetCurrent());

foreach(string role in identity.GetRoles())
{
  Console.WriteLine(roleName);
}


    注意这里所有的方法、参数和返回值都是在编译时参与静态类型检查的,最终封装结果能够象普通对象方法调用一样,直接调用 internal 或 private 的方法、属性、甚至静态方法。而这一看似魔术般的封装,后台实现是完全依赖于标准的透明代理机制。

    关于透明代理的原理和实现机制我这里就不再复述,前者可以google到成堆的资料,后者可以参考我另外两篇底层实现机制的分析文章:
    用WinDbg探索CLR世界 [10] 透明代理实现原理浅析
    用WinDbg探索CLR世界 [10] 透明代理实现原理浅析 - 静态结构

    首先我们来看看上面那段代码,完成这个内部方法的封装,功能上可以分为四个步骤:

    1. 定义内部类型的封装抽象类 WindowsIdentityObject
    2. 将抽象类注册到系统 WrapperManager.RegisterWrapper
    3. 构造对象的封装类实例 WrapperManager.CreateWrapperOfObject
    4. 通过封装类调用对象的内部方法 identity.GetRoles

1. 定义内部类型的封装抽象类

    因为设计上有调用内部类静态函数的需求,因此需要把内部类封装为 InternalClass 和 InternalObject 两级。前者负责封装内部类型,并提供静态函数的调用支持;后者则封装内部对象,提供普通方法的调用支持。
以下为引用:

public abstract class InternalClass : MarshalByRefObject
{
}

public abstract class InternalObject : MarshalByRefObject
{
}


    从 MarshalByRefObject 对象继承是透明代理的要求,实际上就如我在《透明代理实现原理浅析》系列文章中分析的那样,MarshalByRefObject 实际上只是起到一个标记的作用,告诉 CLR 不要对此对象的方法调用进行优化,以便透明代理能够通过方法表接管所有的访问。而因为这个限制,导致此方案必须使用抽象类型,而非语法上更自然的接口来定义内部方法,这方面 Java 的基于接口的要更加灵活一些。

    对使用者来说,只需要通过类似 WindowsIdentityObject 的方式,从这两个类型派生新的封装类,即可获得自动的调用支持。例如 CLR 内部实现了一个 System.Variant 类型,用于可变类型参数的访问:
以下为引用:

public internal struct Variant
{
  internal static int GetCVTypeFromClass(Type ctype);

  public uint ToUInt32()

  public bool IsEmpty { get; }
}


    这里随便选取了一个静态函数、一个类方法和一个属性,要完成对他们的封装,只需要定义 VariantClass 和 VariantObject 两个类型。
以下为引用:

public abstract class VariantClass : InternalClass
{
  public const int CV_I4 = 8;

  public static readonly string CLASS_NAME = "System.Variant";

  public abstract int GetCVTypeFromClass(Type ctype);
}

public abstract class VariantObject : InternalObject
{
  public abstract bool IsEmpty { get; }

  public abstract uint ToUInt32();
}



2. 将抽象类注册到系统

    在合适的时候,将这两个类型注册到系统,以完成对内部类型 System.Variant 的封装。系统在注册的时候,会为类型建立相应的透明代理,以提供后面需要获得的类型层面的封装支持。实现细节等会讨论具体代码时再详细解释。
以下为引用:

WrapperManager.RegisterWrapper(VariantClass.CLASS_NAME, typeof(VariantClass), typeof(VariantObject));



3. 构造对象的封装类实例

    构造封装类的实例可以在类的层面通过 WrapperManager.CreateWrapperOfType 方法来进行;或者在获取类型的封装后,通过它的 InternalClass.CreateInstance 方法来进行;如果需要对现有实例进行包装,则可以通过 WrapperManager.CreateWrapperOfObject 或 InternalClass.CreateWrapperOfObject 方法来进行。
以下为引用:

VariantClass clsVar = (VariantClass)WrapperManager.CreateWrapperOfType(VariantClass.CLASS_NAME);

VariantObject objVar = (VariantObject)clsVar.CreateInstance(15);

WindowsIdentityObject identity = (WindowsIdentityObject)WrapperManager.CreateWrapperOfObject(WindowsIdentity.GetCurrent());


    为了提供封装类一级的 CreateInstance 等方法,必须对 InternalClass 进行扩展,等会讨论具体代码时再详细解释这种实现是如何运作的。
以下为引用:

public abstract class InternalClass : MarshalByRefObject
{
  public abstract InternalObject CreateInstance(params object[] args);

  public abstract InternalObject CreateWrapperOfObject(object obj);
}


    所有这些构造,实际上都是在获取指定封装类的透明代理,为最终的方法调用做准备。

4. 通过封装类调用对象的内部方法

    获得了内部类和内部对象的封装类实例后,就可以通过透明代理,以普通的方法调用语法来调用静态或实例的方法了。
以下为引用:

Assert.AreEqual(clsVar.GetCVTypeFromClass(typeof(Int32)), VariantClass.CV_I4);

Assert.IsFalse(objVar.IsEmpty);

Assert.AreEqual(objVar.ToUInt32(), 15);

foreach(string role in identity.GetRoles())
{
}


    所有的方法调用,都会被透明代理,转发给内部类的相应方法来实现。

    在了解了使用层面的基本情况后,我们来看看整个封装系统的后台是如何运作的。

    首先,WrapperManager 类型维护了一个以内部类型为键、内部类的封装类实例为值的类型包装映射字典,每种类型只能注册一套封装类。
以下为引用:

public class WrapperManager
{
  private static IDictionary _wrappers = new Hashtable();

private WrapperManager()
{
}

  public static void RegisterWrapper(Type typeToWrap, Type classWrapper, Type objectWrapper)
  {
    _wrappers.Add(typeToWrap, CreateWrapperOfType(typeToWrap,
      classWrapper == null ? typeof(InternalClass) : classWrapper,
      objectWrapper == null ? typeof(InternalObject) : objectWrapper));
  }

  public static void RegisterWrapper(string typeToWrap, Type classWrapper, Type objectWrapper)
  {
    RegisterWrapper(Type.GetType(typeToWrap), classWrapper, objectWrapper);
  }

  public static void UnregisterWrapper(Type typeToWrap)
  {
    _wrappers.Remove(typeToWrap);
  }

  public static void UnregisterWrapper(String typeToWrap)
  {
    UnregisterWrapper(Type.GetType(typeToWrap));
  }
}


    所有的封装类构造请求都会先查看是否在缓存中命中,如果没有才去创建新的自动封装支持。而内部对象的封装类创建,则转交给内部类型的封装类去完成。
以下为引用:

public class WrapperManager
{
  public static InternalClass CreateWrapperOfType(Type typeToWrap)
  {
    InternalClass wrapper = (InternalClass)_wrappers[typeToWrap];

    if(wrapper == null)
    {
      wrapper = CreateWrapperOfType(typeToWrap, null, null);

      _wrappers[typeToWrap] = wrapper;
    }

    return wrapper;
  }

  public static InternalClass CreateWrapperOfType(String typeToWrap)
  {
    return CreateWrapperOfType(Type.GetType(typeToWrap));
  }

  public static InternalObject CreateWrapperOfObject(object objectToWrap)
  {
    InternalClass wrapper = CreateWrapperOfType(objectToWrap.GetType());

    return wrapper.CreateWrapperOfObject(objectToWrap);
  }
}


    这样一来,WrapperManager 类型就可以专心于全局封装映射表的维护,而无需担心具体的封装实现。内部类 ClassProxy 会搞定剩下的一切,包括对内部类的类型封装、对内部对象的封装等等。WrapperManager 所需做的仅仅是告诉 ClassProxy 如何去封装,并最终获取封装类实例返回给最终使用者。
以下为引用:

public class WrapperManager
{
  protected static InternalClass CreateWrapperOfType(Type typeToWrap, Type classWrapper, Type objectWrapper)
  {
    ClassProxy proxy = new ClassProxy(typeToWrap,
      classWrapper == null ? typeof(InternalClass) : classWrapper,
      objectWrapper == null ? typeof(InternalObject) : objectWrapper);

    return (InternalClass)proxy.GetTransparentProxy();
  }
}


    注意这里可以忽略内部类和内部对象的特定封装,因为有些情况下是不需要静态方法或对象方法的调用的,此时系统会使用缺省的封装类来进行抽象。

    而专注于内部类和内部对象封装的 ClassProxy 和 ObjectProxy 类,都是从抽象封装代理类 WrapperProxy 集成而来的,层次结构如下:
                                  +-- ClassProxy
    RealProxy <-- WrapperProxy <--+
                                  +-- ObjectProxy

    WrapperProxy 类通过工厂方法模式,提供具体实现无关的方法调用封装机制,也就是最关键的 RealProxy.Invoke 方法,此方法中具体完成对内部类和内部对象的方法的调用。
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected WrapperProxy(Type classToProxy) : base(classToProxy)
{
}

public override IMessage Invoke(IMessage msg)
  {
    // 完成方法调用
  }
}


    通过透明代理发起的方法调用,会被 RealProxy 封装成 IMessage 类型的调用消息。因此第一步工作就是从消息中将方法调用的相关信息解析出来。
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected Type[] getParamTypes(object[] objs)
  {
    Type[] types = new Type[objs.Length];

    for(int i=0; i<types.Length; i++)
    {
      types[i] = objs[i] == null ? null : objs[i].GetType();
    }

    return types;
  }

public override IMessage Invoke(IMessage msg)
  {
    if (msg is IMethodCallMessage)
    {
      IMethodCallMessage msgCall = (IMethodCallMessage)msg;

      Type[] types = getParamTypes(msgCall.Args);

      // 完成方法调用
    }
    else
    {
      Debug.Assert(false, "Unknown message type " + msg.GetType().FullName, msg.ToString());
    }
    return null;
  }
}


    IMethodCallMessage.Args 中保存了方法被调用时的参数数组,通过这个信息,我们可以解析得到目标方法的参数类型数组。getParamTypes 方法完成具体的类型信息获取工作。
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected abstract MethodInfo getMethod(string name, Type[] types, out object target);

public override IMessage Invoke(IMessage msg)
  {
    // 获取方法调用参数类型信息

    Object target;

    MethodInfo method = getMethod(msgCall.MethodName, types, out target);

    if(method == null)
      throw new InvalidOperationException(string.Format("Method {0} is not found", msgCall.MethodName));

    // 完成方法调用
  }
}


    而根据方法的名称和调用参数类型数组,可以通过抽象方法 getMethod 获得实际的方法信息实例,类似于 C++ 中函数指针的概念。因为内部类和内部对象的方法调用目标不同,这里定义了一个抽象 getMethod 方法来屏蔽此区别。
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected static readonly BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.NonPublic;
  protected static readonly BindingFlags ConstructorBindingFlags = DefaultBindingFlags | BindingFlags.Instance;
  protected static readonly BindingFlags MethodBindingFlags = DefaultBindingFlags;

  protected MethodInfo getMethod(Type type, string name, bool instance, Type[] types)
  {
    BindingFlags flags = MethodBindingFlags |
      (instance ? BindingFlags.Instance : BindingFlags.Static);

    return type.GetMethod(name, flags, null, types, null);
  }
}

internal class ClassProxy : WrapperProxy, IInternalClass
{
  private readonly Type _classToProxy, _objectWrapper;

internal ClassProxy(Type classToProxy, Type classWrapper, Type objectWrapper) : base(classWrapper)
{
    _classToProxy = classToProxy;
    _objectWrapper = objectWrapper;
}

  protected override MethodInfo getMethod(string name, Type[] types, out object target)
  {
    target = null;

    return getMethod(_classToProxy, name, false, types);
  }
}

internal class ObjectProxy : WrapperProxy, IInternalObject
{
  private readonly object _object;

public ObjectProxy(Type wrapperClass, object objectToProxy) : base(wrapperClass)
{
    _object = objectToProxy;
}

  protected override MethodInfo getMethod(string name, Type[] types, out object target)
  {
    target = _object;

    return getMethod(target.GetType(), name, true, types);
  }
}


    可以看到 ClassProxy 和 ObjectProxy 实际上是通过 WrapperProxy.getMethod 工具方法,分别从被封装的类型和对象中,尝试获取方法实例的。而 out 类型参数 target,则可以告诉 WrapperProxy.Invoke 在进行实际方法调用时,调用的目标是谁。对内部类的封装类来说,这里需要填入 null 来完成调用。
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected abstract MethodInfo getMethod(string name, Type[] types, out object target);

public override IMessage Invoke(IMessage msg)
  {
    // 获取方法调用参数类型信息

    // 获取方法信息

    try
    {
      return new ReturnMessage(method.Invoke(target, msgCall.Args), null, 0, null, msgCall);
    }
    catch(Exception e)
    {
      return new ReturnMessage(e.InnerException, msgCall);
    }
  }
}


    在获取方法信息后,调用方法就很直接了。通过 MethodInfo.Invoke 调用,捕获可能发生的异常。如果调用成功则构造 ReturnMessage 消息返回调用结果;否则构造 ReturnMessage 消息返回异常信息。注意这里捕获到的异常,都是通过 TargetInvocationException 异常封装过的,因此需要使用 e.InnerException 来解码得到实际发生的异常。

    对于为调用内部类方法而定义的封装类抽象方法来说,如上的代码就足够了。但我们为 InternalClass 等封装增加了 CreateInstance 等等特定的支持,这就需要通过一个小技巧来转发调用了。因为对于 InternalClass 和 InternalObject 的子类,他们都必须是不可能初始化的抽象类,在里面定义方法的实现是没有意义的。我们必须在真实代理这一段,模拟透明代理自身的方法实现,而非简单将之转发给被代理的对象。
    为此可将这些特定方法抽象成新的接口,如
以下为引用:

internal interface IInternalClass
{
  InternalObject CreateInstance(params object[] args);

  InternalObject CreateWrapperOfObject(object obj);

  Type WrappedType { get; }
}

public abstract class InternalClass : MarshalByRefObject, IInternalClass
{
  public abstract InternalObject CreateInstance(params object[] args);

  public abstract InternalObject CreateWrapperOfObject(object obj);

  public abstract Type WrappedType { get; }
}

internal interface IInternalObject
{
  object WrappedObject { get; }
}

public abstract class InternalObject : MarshalByRefObject, IInternalObject
{
  public abstract object WrappedObject { get; }
}


    定义 IInternalClass 和 IInternalObject 接口的作用,仅仅是保障透明代理和真实代理的实现一致性,因此是可选的。
以下为引用:

internal class ObjectProxy : WrapperProxy, IInternalObject
{
  private readonly object _object;

  #region IInternalObject Members

  public object WrappedObject
  {
    get
    {
      return _object;
    }
  }

  #endregion
}


    然后在真实代理中实现相应函数,并修改 WrapperProxy.Invoke 的方法获取算法,如
以下为引用:

internal abstract class WrapperProxy : RealProxy
{
  protected abstract MethodInfo getMethod(string name, Type[] types, out object target);

public override IMessage Invoke(IMessage msg)
  {
    // 获取方法调用参数类型信息

    Object target;

    MethodInfo method = getMethod(msgCall.MethodName, types, out target);
    
    if(method == null)
    {
      target = this;
      method = getMethod(target.GetType(), msgCall.MethodName, true, types);
    }
    

    if(method == null)
      throw new InvalidOperationException(string.Format("Method {0} is not found", msgCall.MethodName));

    // 完成方法调用
  }
}


    新增的代码可以在被封装类型中不存在调用方法时,从真实代理自身来查询方法实现。这样一来对 InternalClass.CreateInstance 的调用,就会因为被封装类型不存在此方法,而被转发到 ClassProxy.CreateInstance 方法上。这个技巧在完成对透明代理的功能增补时非常有用,可以进一步用来模拟 COM 中接口一级的组合语义,以后有机会我再写文章详细讨论。
以下为引用:

internal class ClassProxy : WrapperProxy, IInternalClass
{
  #region IInternalClass Members

  public InternalObject CreateInstance(params object[] args)
  {
    if(args == null) throw new ArgumentNullException("args";

    ConstructorInfo ctor = _classToProxy.GetConstructor(ConstructorBindingFlags, null, getParamTypes(args), null);

    if(ctor == null)
      throw new InvalidOperationException("Not a match constructor to create object.";

    return CreateWrapperOfObject(ctor.Invoke(args));
  }

  public InternalObject CreateWrapperOfObject(object obj)
  {
    ObjectProxy proxy = new ObjectProxy(_objectWrapper, obj);

    return (InternalObject)proxy.GetTransparentProxy();
  }

  public Type WrappedType
  {
    get
    {
      return _classToProxy;
    }
  }

  #endregion
}


    可以看到 ClassProxy.CreateInstance 实际上是通过调用参数,寻找合适的构造函数来创建内部对象,并通过 ClassProxy.CreateWrapperOfObject 方法对其进行封装。

    至此,基于透明代理的内部类访问抽象中,对类型与调用的封装就基本介绍完毕,下一节会详细讨论进一步完成对 out/ref 参数、delegate 等语法特性的模拟与封装机制,以及如何通过此机制进行功能编码简化。

to be continue...