在我询问 “代码中能否对类的某个方法临时改写”,sbwwkmyd 给出其中一种方案的代码,
using System;namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            ClsAAA aaa = new ClsAAA();            using (methodWriter32<Action> method = new methodWriter32<Action>(aaa.ff, new Program().nothing)) 
            {
                aaa.ff();
            }
            Console.ReadKey();
        }        public void nothing()
        {
        }
    }    class ClsAAA
    {
        public void ff()
        {
            Console.WriteLine("OK");
        }
    }    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
    internal struct unionMethod
    {
        internal class point
        {
            public int Point;
        }        [System.Runtime.InteropServices.FieldOffset(0)]
        public point Point;
        [System.Runtime.InteropServices.FieldOffset(0)]
        public object Method;
    }    internal unsafe struct methodTarget
    {
        public int* MethodPoint;
        public object Target;
        public int* TargetPoint;
    }    internal class targetPoint
    {
        public object Target;
    }    public unsafe abstract class methodWriterBase32<methodType> : IDisposable
    {
        private int* methodPoint;
        private int method;
        public methodWriterBase32(methodType setMethod, methodType getMethod)
        {
            if (setMethod == null || getMethod == null) throw null;
            methodPoint = getPoint(setMethod);
            int* getMethodPoint = getPoint(getMethod);
            if (methodPoint == getMethodPoint) throw null;
            method = *methodPoint;
            *methodPoint = *getMethodPoint;
        }        protected abstract int* getPoint(methodType method);
        public void Set(methodType method)
        {
            if (method == null) throw null;
            *methodPoint = *getPoint(method);
        }        public void Dispose()
        {
            if (method != 0)
            {
                *methodPoint = method;
                method = 0;
            }
        }
    }    public sealed unsafe class staticMethodWriter32<methodType> : methodWriterBase32<methodType>
    {
        public staticMethodWriter32(methodType setMethod, methodType getMethod) : base(setMethod, getMethod) { }
        protected override int* getPoint(methodType method)
        {
            Delegate methodDelegate = method as Delegate;
            if (!methodDelegate.Method.IsStatic) throw null;
            unionMethod union = new unionMethod { Method = method };
            return (int*)*((int*)*((int*)union.Point.Point + 4) + 2) + 2;
        }
    }    public sealed unsafe class methodWriter32<methodType> : methodWriterBase32<methodType>
    {
        public methodWriter32(methodType setMethod, methodType getMethod) : base(setMethod, getMethod) { }
        protected override int* getPoint(methodType method)
        {
            Delegate methodDelegate = method as Delegate;
            if (methodDelegate.Method.IsStatic) throw null;
            unionMethod union = new unionMethod { Method = method };
            int point = union.Point.Point;
            int* methodPoint = (int*)point;
            while (*methodPoint != point) ++methodPoint;
            return (int*)*((int*)*(methodPoint + 2) + 2) + 2;
        }
    }
}
但我在.NET3.5环境下,不安全代码选择打勾,程序运行出错,
在类 methodWriterBase32 的构建函数中 method = *methodPoint; 这句话,错误信息:
“尝试读取或写入受保护的内存。这通常指示其他内存已损坏”我也试着改为 .NET 2.0 但它不允许这种写法:
using (methodWriter32<Action> method = new methodWriter32<Action>(aaa.ff, new ClsBBB().ff)) 也试着两个分成两个项目,一个用2.0,一个用3.5,仍然是 method = *methodPoint; 这句话同样错误。
我对C#中指针不熟悉,大家能帮我看一下是什么原因吗?

解决方案 »

  1.   

    这种方法除了展示一种特别的技巧以外,基本没有什么实际意义,对于你做mock来说。
      

  2.   

    我#19代码中联合体的定义是错误的,指针定位也是错误的。可以参考fastCSharp.reflection.methodWriter,我这里给个重新写个只支持32位的简易版本        /// <summary>
            /// 委托方法与指针联合体
            /// </summary>
            [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
            internal struct methodPointUnion
            {
                /// <summary>
                /// 委托方法
                /// </summary>
                internal sealed class method
                {
                    /// <summary>
                    /// 委托方法
                    /// </summary>
                    public object Method;
                }
                /// <summary>
                /// 委托指针
                /// </summary>
                private sealed class point
                {
                    /// <summary>
                    /// 委托指针
                    /// </summary>
                    public int Point;
                }
                /// <summary>
                /// 委托指针
                /// </summary>
                [System.Runtime.InteropServices.FieldOffset(0)]
                private point methodPoint;
                /// <summary>
                /// 委托方法
                /// </summary>
                [System.Runtime.InteropServices.FieldOffset(0)]
                public method Method;
                /// <summary>
                /// 委托指针
                /// </summary>
                public unsafe int* Point
                {
                    get
                    {
                        int* point = (int*)methodPoint.Point + 4;
                        if (*point == 0) --point;
                        return (int*)*((int*)*point + 2) + 2;
                    }
                }
            }
            /// <summary>
            /// 方法重写
            /// </summary>
            /// <typeparam name="methodType">委托类型</typeparam>
            public sealed unsafe class methodWriter<methodType> : IDisposable
            {
                /// <summary>
                /// 方法指针
                /// </summary>
                private int* methodPoint;
                /// <summary>
                /// 方法指针值
                /// </summary>
                private int method;
                /// <summary>
                /// 是否静态方法
                /// </summary>
                private bool isStatic;
                /// <summary>
                /// 方法重写
                /// </summary>
                /// <param name="method">被重写方法委托</param>
                /// <param name="setMethod">待写入的方法委托</param>
                public methodWriter(methodType method, methodType setMethod)
                {
                    Delegate methodDelegate = method as Delegate;
                    if (methodDelegate == null || setMethod == null) throw null;
                    Delegate setMethodDelegate = setMethod as Delegate;
                    isStatic = methodDelegate.Method.IsStatic;
                    if (isStatic ^ setMethodDelegate.Method.IsStatic) throw null;
                    if (!isStatic)
                    {
                        if (methodDelegate.Target != null && !methodDelegate.Target.GetType().IsClass) throw null;
                        if (setMethodDelegate.Target != null && !methodDelegate.Target.GetType().IsClass) throw null;
                    }
                    methodPointUnion union = new methodPointUnion { Method = new methodPointUnion.method { Method = methodDelegate } };
                    int* methodPoint = union.Point;
                    union.Method.Method = setMethodDelegate;
                    int* setMethodPoint = union.Point;
                    if (methodPoint == setMethodPoint) throw null;
                    this.method = *(this.methodPoint = methodPoint);
                    *methodPoint = *setMethodPoint;
                }
                /// <summary>
                /// 方法重写
                /// </summary>
                /// <param name="setMethod">待写入的方法委托</param>
                public void Set(methodType setMethod)
                {
                    Delegate setMethodDelegate = setMethod as Delegate;
                    if (setMethodDelegate == null) throw null;
                    if (isStatic ^ setMethodDelegate.Method.IsStatic) throw null;
                    if (!isStatic && setMethodDelegate.Target != null && !setMethodDelegate.Target.GetType().IsClass) throw null;
                    methodPointUnion union = new methodPointUnion { Method = new methodPointUnion.method { Method = setMethodDelegate } };
                    *methodPoint = *union.Point;
                }
                /// <summary>
                /// 释放方法指针
                /// </summary>
                public void Dispose()
                {
                    int method = this.method;
                    this.method = 0;
                    if (method != 0) *methodPoint = method;
                }
            }        class ClsAAA
            {
                public void ff()
                {
                    Console.WriteLine("OK");
                }
            }
            public void nothing()
            {
            }
            static void Main(string[] args)
            {            ClsAAA aaa = new ClsAAA();
                using (methodWriter<action> method = new methodWriter<action>(aaa.ff, new Program().nothing))
                {
                    aaa.ff();
                }
                Console.ReadKey();
            }技巧确实是没有意义的,我写程序都是为了更方便的解决实际问题。
      

  3.   

    谢谢sbwwkmyd 的回复,也谢谢caozhy的参与,它确实只是我的其中一种方法,但我对它兴趣主要原因是因为我对它不懂,因此对它的调试可以加强我对系统的一些了解。由于我做的系统不是拿去卖钱的,只是给个别用户用,所以,在系统中出现各种各样的方法对我们来说不会有太大问题。
      

  4.   

    需要修改一下,否则不能支持 虚函数 或者 泛型函数            public unsafe int* Point
                {
                    get
                    {
                        int* point = (int*)methodPoint.Point + 4;
                        if (*point == 0) --point;
                        point = (int*)*((int*)*point + 2);
                        if ((*point & 0x8000000) != 0) return point + 5;
                        if ((*point & 0x40000000) == 0) return point + 18;
                        return point + 2;
                    }
                }
      

  5.   

    to:sbwwkmyd 
    现有几个疑问:.Net 2.0 应该不能这样写吧?
    using (methodWriter<action> method = new methodWriter<action>(aaa.ff, new Program().nothing))
    它会提示:使用泛型 类型“System.Action<T>”需要“1”个类型实参
    .Net 2.0 下对应的写法应该是如何?假定您的代码对 .Net 3.5 适应。我测试的结果是,编译通过,但运行时出错:
    this.method = *(this.methodPoint = methodPoint);
    尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
    出错时:methodPoint 值为 0x8d50dbf1
    同时VS也显示:*methodPoint = 无法取消对“methodPoint”的引用。指针无效。
    倒查结果:
    methodPoint 是在 int* methodPoint = union.Point; 赋值的
    此时的 union 有值
    [size=9px]union        {ConsoleApplication3.methodPointUnion}         ConsoleApplication3.methodPointUnion
     Method      {ConsoleApplication3.methodPointUnion.method}  ConsoleApplication3.methodPointUnion.method
      Method     {Method = {Void nothing()}}                    object {System.Action}
       base      {Method = {Void nothing()}}                    System.MulticastDelegate {System.Action}
     methodPoint {ConsoleApplication3.methodPointUnion.method}  ConsoleApplication3.methodPointUnion.point {ConsoleApplication3.methodPointUnion.method}
     Point       0x8d5173f1                                     int*
      *union.Point 无法取消对“*union.Point”的引用。指针无效。 int[/size]
      

  6.   

    union        {ConsoleApplication3.methodPointUnion}         ConsoleApplication3.methodPointUnion
     Method      {ConsoleApplication3.methodPointUnion.method}  ConsoleApplication3.methodPointUnion.method
      Method     {Method = {Void nothing()}}                    object {System.Action}
       base      {Method = {Void nothing()}}                    System.MulticastDelegate {System.Action}
     methodPoint {ConsoleApplication3.methodPointUnion.method}  ConsoleApplication3.methodPointUnion.point {ConsoleApplication3.methodPointUnion.method}
     Point       0x8d5173f1                                     int*
      *union.Point 无法取消对“*union.Point”的引用。指针无效。 int
      

  7.   

    继续倒着查int* point = (int*)methodPoint.Point + 4;
      methodPoint.Point=20134628(0x1333ae4), point=0x01333af4(疑问1:加4后为什么不是e8,而是f4。疑问2:我从调试窗口中的内存数据窗口中,输入Point-4,亦是显示0x1333ae4地址之内存数据,而不是显示0x1333af0地址之内存数据,为什么?自已解释为对 int* +1就是指内存地址+4)
    if (*point == 0) --point;
      point地址内存为0,扣减1后变为 0x1333af0,内存此处有一个值(a0 c5 9d 00)
    return (int*)*((int*)*point + 2) + 2;
      *point=0x009dc5a0地址+2后,按上面的说法,应该是:0x009dc5a8,对应内存值为(e9 db 50 8d),但我再去0x8d50dbe9位置查看内容时,内存全为 ?? ?? ??在内存跳来跳去的过程中,这个结构是什么标准?我们是怎么知道的?
      

  8.   

    自定义一个委托        public delegate void action();0x8d50dbf1是系统空间地址,不是用户空间地址,这个值肯定是错误的。int* +1就是指内存地址+4,这个是对的。
    0x009dc5a0还是对的,后面的return就错了。#6已经改成            public unsafe int* Point
                {
                    get
                    {
                        int* point = (int*)methodPoint.Point + 4;//静态函数指针
                        if (*point == 0) --point;//非静态函数,取动态成员函数指针
                        point = (int*)*((int*)*point + 2);
                        if ((*point & 0x8000000) != 0) return point + 5;//泛型函数指针
                        if ((*point & 0x40000000) == 0) return point + 18;//虚函数指针
                        return point + 2;//普通函数指针
                    }
                }0x80000000不是用户空间地址,所以肯定是错误的。不过看你的调试信息,就算改成这样point = (int*)*((int*)*point + 2);取到的值还是错误的。
    这个内存结构没有标准,不过受影响的因素应该只是CLR与JIT的版本。
      

  9.   

    我想知道,在                    int* point = (int*)methodPoint.Point + 4;
                        if (*point == 0) --point;
    之后,你的*point(也就是你调试信息中的*0x009dc5a0)的内存数据是什么,是不是一个call 16B指令,我本地是 e8 xx xx 49 79 5e 00 00,接下来四个字节就是函数信息表的地址了。
      

  10.   

    0x009DC5B0  e8 05 58 49 79 5e 00 01 e9 cb 50 8d 00 5f 04 00  ..XIy^....P.._..
    0x009DC5C0  dc 9a 9d 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    0x009DC5D0  e8 e5 57 49 79 5e 00 03 e8 dd 57 49 79 5e 05 02  ..WIy^....WIy^..
    0x009DC5E0  e8 d5 57 49 79 5e 0a 01 e8 cd 57 49 79 5e 0f 00  ..WIy^....WIy^..
    0x009DC5F0  4c 9b 9d 00 00 00 00 00 00 00 00 00 00 00 00 00  L...............
    0x009DC600  00 00 00 00 b8 60 9b 9d 00 89 ed e9 bc 58 a3 ff  .....`.......X..
    0x009DC610  b8 74 9b 9d 00 89 ed e9 b0 58 a3 ff b8 88 9b 9d  .t.......X......
    0x009DC620  00 89 ed e9 a4 58 a3 ff e8 8d 57 49 79 5e 00 02  .....X....WIy^..
    反汇编结果:
    xxxx:0100 E80558   CALL 5908
    xxxx:0103 49       DEC CX
    xxxx:0104 795E     JNS Short 0164
    xxxx:0106 0001     ADD [BX+DI],AL
    xxxx:0108 E9CB50   JMP 51D6
    其中0x009DC5B7(107)位置你是00,我的是01,这一点有差异。接下来的四个字节:e9 cb 50 8d,所以就变得0x80000000了.感觉这是有点偏了,这对程序的稳定性会不会大打折扣?
      

  11.   

    看你的函数信息表数据应该+4,也就是0x009DC5C0的0x9d9adc。
    如果无法确定的话,看来应该找到那个与0x009DC5B0比较接近的值。
      

  12.   

    因为C#函数是及时编译的,所以判定函数信息表比较容易。
    也就是函数第一次执行之前,函数信息表的第一个int&0x30000000=0;执行之后函数信息表的第一个int&0x30000000=0x30000000。另外函数信息表中的函数指针也会被修改而变化。
      

  13.   

    虽然改为+4,程序不会出错,但也不是想像替换的结果,仍然显示出来“OK”来。
    debug数据如下:
    methodPoint.Point:13795c4
    int* point = (int*)methodPoint.Point + 4;
                point:1333b44
               *point:0000
    if (*point == 0) --point;
               *point:9dc580
       (int*)*point+4:9dc590
    *((int*)*point+4):9d9adc
    point = (int*)*((int*)*point + 4);
                point:9d9adc
               *point:4b00000e    ??????
              point+2:9d9ae4
           *(point+2):9dc580
           *(point+5):0004
          *(point+18):79286a90其中问号的 *point 为 4b00000e,与8000000 与一下,就变成泛型函数了?而跳过 泛型函数的判断,让其 +2,结果又回到 9dc580 ,程序感觉上没有变化。
    0x009DC580  e8 35 58 49 79 5e 00 01 e9 fb 50 8d 00 5f 04 00  .5XIy^....P.._..
    0x009DC590  dc 9a 9d 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    0x009DC5A0  e8 15 58 49 79 5e 00 03 e8 0d 58 49 79 5e 05 02  ..XIy^....XIy^..
    0x009DC5B0  e8 05 58 49 79 5e 0a 01 e8 fd 57 49 79 5e 0f 00  ..XIy^....WIy^..
    0x009DC5C0  4c 9b 9d 00 00 00 00 00 00 00 00 00 00 00 00 00  L...............
    ==========
    0x009D9ADC  0e 00 00 4b 05 00 00 00 80 c5 9d 00 00 00 00 00  ...K....€.......
    0x009D9AEC  0f 00 04 3b 04 00 00 00 94 7b 8d 00 00 00 00 00  ...;.....{......
    0x009D9AFC  0c 00 00 00 11 00 05 00 04 00 00 00 88 08 33 79  ..............3y
    0x009D9B0C  58 8c 9d 00 34 9b 9d 00 4c 70 2c 01 00 00 00 00  X...4...Lp,.....
    0x009D9B1C  00 00 00 00 70 6a 28 79 90 6a 28 79 00 6b 28 79  ....pj(y.j(y.k(y
    0x009D9B2C  60 76 2f 79 88 c5 9d 00 80 00 00 00 18 5d 31 02  `v/y....€....]1.
      

  14.   

    是有点复杂了。该功能没用上。谢谢sbwwkmyd热心的讲解,结贴。