找回密码
 立即注册

QQ登录

只需一步,快速开始

thrall

超级版主

11

主题

170

帖子

1909

积分

超级版主

Rank: 8Rank: 8

积分
1909

活字格认证微信认证勋章

thrall
超级版主   /  发表于:2013-5-7 11:21  /   查看:4929  /  回复:0
有人问如何在保留装箱对象的前提下修改值?



场景:
  1. object obj = 100;
  2. Console.WriteLine("original object value: " + obj.ToString()); // when debug, make obj's ID: 1#
  3. //TODO: modify obj value here (to 1000, for example), but preserve obj object
  4. Console.WriteLine("modified object value: " + obj.ToString()); // make sure obj's ID: 1#
复制代码

分析:
显然这里直接obj = 1000是不行的,那样之后得到的是对1000装箱的对象,而不是对100的装箱对象了,那么如何修改呢?  
首先,这里列出本文涉及的一些.NET和CLR的准备知识——装箱的对象的分配和存储、对象的托管内存地址获取、对象唯一性确定、托管内存数据读写。如果你不是很熟悉,没关系,经过本篇的实践,加上MSDN的解释,你很快就可以理解。  
1、对象的分配和存储。这里设计的仅仅是部分,细节可以参考CLR via。对象分配在托管堆上,由几个部分组成,第一部分是存储的是对象类型的TypeHandle,其后内容随类型不同而不同;对于装箱对象,其后紧跟的内存存储的是装箱的值(就是我们要找到然后去修改的东东了)。  
2、对象的托管内存地址获取。通过System.Runtime.InteropServices.GCHandle类和其上的静态方法获取。  
3、对象唯一性确定。这个方法有两种,第一种,需要依赖VS IDE Debug环境,在IDE的debug下,可以对任何对象设置对象标识(object ID),通过对象标识,就可以知道对象的往生来去了。另一种办法则是利用第二条知识,使用GCHandle的IsAllocated来判断。  
4、通过上面得到了托管地址,如何修改托管地址处保存的内容呢?使用System.Runtime.InteropServices.Marshal.StructureToPtr或者System.Runtime.InteropServices.Marshal.WriteXXX系列方法即可。  
基于以上内容,我们可以可以做到在保留装箱对象的前提下修改值了,显然首先需要的是装箱对象的引用,然后调用System.Runtime.InteropServices.GCHandle.Aloc(object)得到托管地址,该托管地址指向的内容就是装箱的对象;由于装箱对象的第一部分是TypeHandle,所以需要将指针向后偏移IntPtr.Size得到数据存储地址,然后通过Marshal.StructureToPtr写入新的内容即可。代码片段如下
  1. if (!_memData.IsAllocated)
  2. {
  3.     _memData = GCHandle.Alloc(_boxedObject);
  4. }
  5. IntPtr pMemData = GCHandle.ToIntPtr(_memData);
  6. IntPtr pBox = new IntPtr((Marshal.ReadIntPtr(pMemData).ToInt64() + IntPtr.Size));
  7. Marshal.StructureToPtr(value, pBox, false);
复制代码
结果:
讨论:
显然这里写入数据时候是需要很小心的,因为如果装箱的数据占用内存小,而写入的数据比它大的话,就会触发AccessViolationException,甚至导致溢出,形成安全漏洞。额外话题:  
如果传入的就是一个引用类型的实例,会是什么结果呢?  
还等什么呢,赶快自己动手试试喽。 附录,完整的测试代码:
  1. using System;
  2. using System.Runtime.InteropServices;

  3. namespace BoxedObjectWriter
  4. {
  5.     class Program
  6.     {
  7.         static void Main(string[] args)
  8.         {
  9.             object test = 100;
  10.             Console.WriteLine("original value=" + test.ToString() + ", hash=" + test.GetHashCode());
  11.             BoxedObject b = new BoxedObject(test);
  12.             b.Value = 1000;
  13.             Console.WriteLine("after edit value=" + test.ToString() + ", hash=" + test.GetHashCode());

  14.             Console.ReadLine();
  15.         }
  16.     }

  17.     public class BoxedObject : IDisposable
  18.     {
  19.         private object _boxedObject;
  20.         private GCHandle _memData;

  21.         public BoxedObject(object boxObject)
  22.         {
  23.             if (boxObject == null)
  24.             {
  25.                 throw new ArgumentNullException();
  26.             }
  27.             _boxedObject = boxObject;            
  28.         }

  29.         ~BoxedObject()
  30.         {
  31.             (this as IDisposable).Dispose();
  32.         }

  33.         public object Value
  34.         {
  35.             get { return _boxedObject; }
  36.             set
  37.             {
  38.                 if (value == null)
  39.                 {
  40.                     throw new ArgumentNullException();
  41.                 }
  42.                 if (value.GetType() != _boxedObject.GetType())
  43.                 {
  44.                     throw new NotSupportedException(string.Format("Can not set [{0}] value to [{1}] object",
  45.                         value.GetType().Name,
  46.                         _boxedObject.GetType().Name));
  47.                 }

  48.                 if (!_memData.IsAllocated)
  49.                 {
  50.                     _memData = GCHandle.Alloc(_boxedObject);
  51.                 }
  52.                 IntPtr pMemData = GCHandle.ToIntPtr(_memData);
  53.                 IntPtr pBox = new IntPtr((Marshal.ReadIntPtr(pMemData).ToInt64() + IntPtr.Size));
  54.                 Marshal.StructureToPtr(value, pBox, false);
  55.             }
  56.         }

  57.         IDisposable Members
  58.     }
  59. }
复制代码

0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部